Merge pull request #1240 from ajaxorg/textarea

make ACE work better as a textarea replacement.
This commit is contained in:
Lennart Kats 2013-02-22 03:45:58 -08:00
commit 92786afa7c
13 changed files with 187 additions and 95 deletions

View file

@ -360,31 +360,32 @@ bindCheckbox("read_only", function(checked) {
env.editor.setReadOnly(checked);
});
var secondSession = null;
bindDropdown("split", function(value) {
var sp = env.split;
if (value == "none") {
if (sp.getSplits() == 2) {
secondSession = sp.getEditor(1).session;
}
if (value == "none") {
sp.setSplits(1);
} else {
var newEditor = (sp.getSplits() == 1);
if (value == "below") {
sp.setOrientation(sp.BELOW);
} else {
sp.setOrientation(sp.BESIDE);
}
sp.setOrientation(value == "below" ? sp.BELOW : sp.BESIDE);
sp.setSplits(2);
if (newEditor) {
var session = secondSession || sp.getEditor(0).session;
var session = sp.getEditor(0).session;
var newSession = sp.setSession(session, 1);
newSession.name = session.name;
}
}
});
function synchroniseScrolling() {
var s1 = env.split.$editors[0].session;
var s2 = env.split.$editors[1].session;
s1.on('changeScrollTop', function(pos) {s2.setScrollTop(pos)});
s2.on('changeScrollTop', function(pos) {s1.setScrollTop(pos)});
s1.on('changeScrollLeft', function(pos) {s2.setScrollLeft(pos)});
s2.on('changeScrollLeft', function(pos) {s1.setScrollLeft(pos)});
}
bindCheckbox("highlight_token", function(checked) {
var editor = env.editor;
if (editor.tokenTooltip && !checked) {

View file

@ -88,6 +88,7 @@ require("ace/commands/default_commands").commands.push({
exec: function(editor) {
dom.toggleCssClass(document.body, "fullScreen")
dom.toggleCssClass(editor.container, "fullScreen")
editor.setAutoScrollEditorIntoView()
editor.resize()
}
})
@ -96,7 +97,7 @@ require("ace/commands/default_commands").commands.push({
var editor = ace.edit("editor");
editor.setTheme("ace/theme/twilight");
editor.session.setMode("ace/mode/javascript");
editor.setAutoScrollEditorIntoView();
var count = 1;
function add() {
@ -113,6 +114,8 @@ function add() {
editor = ace.edit(el)
editor.setTheme(theme)
editor.session.setMode("ace/mode/javascript")
editor.setAutoScrollEditorIntoView()
editor.setValue([
"this is editor number: ", count, "\n",

View file

@ -8,7 +8,7 @@ var EventEmitter = require("../lib/event_emitter").EventEmitter;
/**
* @class CommandManager
*
*
*
**/
/**
@ -16,7 +16,7 @@ var EventEmitter = require("../lib/event_emitter").EventEmitter;
* @param {String} platform Identifier for the platform; must be either `'mac'` or `'win'`
* @param {Array} commands A list of commands
*
*
*
*
*
**/
@ -27,7 +27,7 @@ var CommandManager = function(platform, commands) {
this.commmandKeyBinding = {};
this.addCommands(commands);
this.setDefaultHandler("exec", function(e) {
return e.command.exec(e.editor, e.args || {});
});
@ -49,11 +49,9 @@ oop.inherits(CommandManager, HashHandler);
if (editor && editor.$readOnly && !command.readOnly)
return false;
var retvalue = this._emit("exec", {
editor: editor,
command: command,
args: args
});
var e = {editor: editor, command: command, args: args};
var retvalue = this._emit("exec", e);
this._signal("afterExec", e);
return retvalue === false ? false : true;
};

View file

@ -717,9 +717,7 @@ var EditSession = function(text, mode) {
clazz = "ace_step";
var range = new Range(startRow, 0, endRow, Infinity);
var id = this.addMarker(range, clazz, "fullLine", inFront);
range.id = id;
range.id = this.addMarker(range, clazz, "fullLine", inFront);
return range;
};
@ -999,11 +997,11 @@ var EditSession = function(text, mode) {
**/
this.setScrollTop = function(scrollTop) {
scrollTop = Math.round(Math.max(0, scrollTop));
if (this.$scrollTop === scrollTop)
if (this.$scrollTop === scrollTop || isNaN(scrollTop))
return;
this.$scrollTop = scrollTop;
this._emit("changeScrollTop", scrollTop);
this._signal("changeScrollTop", scrollTop);
};
/**
@ -1020,11 +1018,11 @@ var EditSession = function(text, mode) {
**/
this.setScrollLeft = function(scrollLeft) {
scrollLeft = Math.round(Math.max(0, scrollLeft));
if (this.$scrollLeft === scrollLeft)
if (this.$scrollLeft === scrollLeft || isNaN(scrollLeft))
return;
this.$scrollLeft = scrollLeft;
this._emit("changeScrollLeft", scrollLeft);
this._signal("changeScrollLeft", scrollLeft);
};
/**

View file

@ -507,10 +507,13 @@ var Editor = function(renderer, session) {
session.removeMarker(session.$highlightLineMarker.id);
session.$highlightLineMarker = null;
} else if (!session.$highlightLineMarker && highlight) {
session.$highlightLineMarker = session.highlightLines(highlight.row, highlight.row, "ace_active-line");
var range = new Range(highlight.row, highlight.column, highlight.row, Infinity);
range.id = session.addMarker(range, "ace_active-line", "screenLine");
session.$highlightLineMarker = range;
} else if (highlight) {
session.$highlightLineMarker.start.row = highlight.row;
session.$highlightLineMarker.end.row = highlight.row;
session.$highlightLineMarker.start.column = highlight.column;
session._emit("changeBackMarker");
}
};
@ -2107,6 +2110,62 @@ var Editor = function(renderer, session) {
this.renderer.destroy();
this._emit("destroy", this);
};
/**
* Enables automatic scrolling of the cursor into view when editor itself is inside scrollable element
* @param {Boolean} enable default true
**/
this.setAutoScrollEditorIntoView = function(enable) {
if (enable === true)
return;
var rect;
var self = this;
var shouldScroll = false;
if (!this.$scrollAnchor)
this.$scrollAnchor = document.createElement("div");
var scrollAnchor = this.$scrollAnchor;
scrollAnchor.style.cssText = "position:absolute";
this.container.insertBefore(scrollAnchor, this.container.firstChild);
var onChangeSelection = this.on("changeSelection", function() {
shouldScroll = true;
});
// needed to not trigger sync reflow
var onBeforeRender = this.renderer.on("beforeRender", function() {
if (shouldScroll)
rect = self.renderer.container.getBoundingClientRect();
});
var onAfterRender = this.renderer.on("afterRender", function() {
if (shouldScroll && rect && self.isFocused()) {
var renderer = self.renderer;
var pos = renderer.$cursorLayer.$pixelPos;
var config = renderer.layerConfig;
var top = pos.top - config.offset;
if (pos.top >= 0 && top + rect.top < 0) {
shouldScroll = true;
} else if (pos.top < config.height &&
pos.top + rect.top + config.lineHeight > window.innerHeight) {
shouldScroll = false;
} else {
shouldScroll = null;
}
if (shouldScroll != null) {
scrollAnchor.style.top = top + "px";
scrollAnchor.style.left = pos.left + "px";
scrollAnchor.style.height = config.lineHeight + "px";
scrollAnchor.scrollIntoView(shouldScroll);
}
shouldScroll = rect = null;
}
});
this.setAutoScrollEditorIntoView = function(enable) {
if (enable === false)
return;
delete this.setAutoScrollEditorIntoView;
this.removeEventListener("changeSelection", onChangeSelection);
this.renderer.removeEventListener("afterRender", onAfterRender);
this.renderer.removeEventListener("beforeRender", onBeforeRender);
};
};
}).call(Editor.prototype);

View file

@ -374,9 +374,12 @@ var TextInput = function(parentNode, host) {
resetSelection(host.selection.isEmpty());
host._emit("nativecontextmenu", {target: host});
var rect = host.container.getBoundingClientRect();
var style = dom.computedStyle(host.container);
var top = rect.top + (parseInt(style.borderTopWidth) || 0);
var left = rect.left + (parseInt(rect.borderLeftWidth) || 0);
var move = function(e) {
text.style.left = e.clientX - rect.left - 2 + "px";
text.style.top = e.clientY - rect.top - 2 + "px";
text.style.left = e.clientX - left - 2 + "px";
text.style.top = e.clientY - top - 2 + "px";
};
move(e);

View file

@ -170,7 +170,7 @@ var Cursor = function(parentEl) {
selections = [{cursor: null}];
}
for (var i = selections.length; i--; ) {
for (var i = 0, n = selections.length; i < n; i++) {
var pixelPos = this.getPixelPosition(selections[i].cursor, true);
if ((pixelPos.top > config.height + config.offset ||
pixelPos.top < -config.offset) && i > 1) {

View file

@ -82,6 +82,8 @@ var Marker = function(parentEl) {
marker.renderer(html, range, left, top, config);
} else if (marker.type == "fullLine") {
this.drawFullLineMarker(html, range, marker.clazz, config);
} else if (marker.type == "screenLine") {
this.drawScreenLineMarker(html, range, marker.clazz, config);
} else if (range.isMultiLine()) {
if (marker.type == "text")
this.drawTextMarker(html, range, marker.clazz, config);
@ -194,7 +196,19 @@ var Marker = function(parentEl) {
"top:", top, "px;",
"left:0;right:0;'></div>"
);
}
};
this.drawScreenLineMarker = function(stringBuilder, range, clazz, config) {
var top = this.$getTop(range.start.row, config);
var height = config.lineHeight;
stringBuilder.push(
"<div class='", clazz, "' style='",
"height:", height, "px;",
"top:", top, "px;",
"left:0;right:0;'></div>"
);
};
}).call(Marker.prototype);

View file

@ -32,11 +32,13 @@ define(function(require, exports, module) {
"use strict";
var EventEmitter = {};
var stopPropagation = function() { this.propagationStopped = true; };
var preventDefault = function() { this.defaultPrevented = true; };
EventEmitter._emit =
EventEmitter._dispatchEvent = function(eventName, e) {
this._eventRegistry = this._eventRegistry || {};
this._defaultHandlers = this._defaultHandlers || {};
this._eventRegistry || (this._eventRegistry = {});
this._defaultHandlers || (this._defaultHandlers = {});
var listeners = this._eventRegistry[eventName] || [];
var defaultHandler = this._defaultHandlers[eventName];
@ -48,18 +50,12 @@ EventEmitter._dispatchEvent = function(eventName, e) {
if (!e.type)
e.type = eventName;
if (!e.stopPropagation) {
e.stopPropagation = function() {
this.propagationStopped = true;
};
}
if (!e.preventDefault) {
e.preventDefault = function() {
this.defaultPrevented = true;
};
}
if (!e.stopPropagation)
e.stopPropagation = stopPropagation;
if (!e.preventDefault)
e.preventDefault = preventDefault;
if (!e.target)
e.target = this;
for (var i=0; i<listeners.length; i++) {
listeners[i](e);
@ -71,6 +67,26 @@ EventEmitter._dispatchEvent = function(eventName, e) {
return defaultHandler(e);
};
EventEmitter._signal = function(eventName, e) {
var listeners = (this._eventRegistry || {})[eventName];
if (!listeners)
return;
for (var i=0; i<listeners.length; i++)
listeners[i](e);
};
EventEmitter.once = function(eventName, callback) {
var _self = this;
var newCallback = function() {
fun && fun.apply(null, arguments);
_self.removeEventListener(event, newCallback);
};
this.addEventListener(event, newCallback);
};
EventEmitter.setDefaultHandler = function(eventName, callback) {
this._defaultHandlers = this._defaultHandlers || {};
@ -81,7 +97,7 @@ EventEmitter.setDefaultHandler = function(eventName, callback) {
};
EventEmitter.on =
EventEmitter.addEventListener = function(eventName, callback) {
EventEmitter.addEventListener = function(eventName, callback, capturing) {
this._eventRegistry = this._eventRegistry || {};
var listeners = this._eventRegistry[eventName];
@ -89,7 +105,8 @@ EventEmitter.addEventListener = function(eventName, callback) {
listeners = this._eventRegistry[eventName] = [];
if (listeners.indexOf(callback) == -1)
listeners.push(callback);
listeners[capturing ? "unshift" : "push"](callback);
return callback;
};
EventEmitter.removeListener =

View file

@ -297,28 +297,18 @@ function DefaultHandlers(mouseHandler) {
};
this.onMouseWheel = function(ev) {
if (ev.getShiftKey() || ev.getAccelKey()){
if (ev.getShiftKey() || ev.getAccelKey())
return;
}
var t = ev.domEvent.timeStamp;
var dt = t - (this.$lastScrollTime||0);
var editor = this.editor;
var isScrolable = editor.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed);
if (isScrolable) {
this.$passScrollEvent = false;
} else {
if (this.$passScrollEvent)
return;
if (!this.$scrollStopTimeout) {
var self = this;
this.$scrollStopTimeout = setTimeout(function() {
self.$passScrollEvent = true;
self.$scrollStopTimeout = null;
}, 200);
}
if (isScrolable || dt < 200) {
this.$lastScrollTime = t;
editor.renderer.scrollBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed);
return ev.stop();
}
editor.renderer.scrollBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed);
return ev.preventDefault();
};
}).call(DefaultHandlers.prototype);

View file

@ -77,7 +77,7 @@ function FoldHandler(editor) {
while (i >= 0) {
var c = fw[i];
if (c == null)
c = fw[i] = session.getFoldWidget();
c = fw[i] = session.getFoldWidget(i);
if (c == "start") {
var range = session.getFoldWidgetRange(i);

View file

@ -66,7 +66,7 @@ var EditSession = require("./edit_session").EditSession;
// automatically sorted list of ranges
this.rangeList = null;
/**
/**
* Adds a range to a selection by entering multiselect mode, if necessary.
* @param {Range} range The new range to add
* @param {Boolean} $blockChangeEvents Whether or not to block changing events
@ -218,7 +218,7 @@ var EditSession = require("./edit_session").EditSession;
var start = range.end, end = range.start;
else
var start = range.start, end = range.end;
this.addRange(Range.fromPoints(end, end));
this.addRange(Range.fromPoints(start, start));
return;
@ -261,9 +261,9 @@ var EditSession = require("./edit_session").EditSession;
};
/**
*
*
* Gets list of ranges composing rectangular block on the screen
*
*
* @param {Cursor} screenCursor The cursor to use
* @param {Anchor} screenAnchor The anchor to use
* @param {Boolean} includeEmptyLines If true, this includes ranges inside the block which are empty due to clipping
@ -339,8 +339,8 @@ var EditSession = require("./edit_session").EditSession;
var Editor = require("./editor").Editor;
(function() {
/**
*
/**
*
* Updates the cursor and marker layers.
* @method Editor.updateSelectionMarkers
*
@ -350,7 +350,7 @@ var Editor = require("./editor").Editor;
this.renderer.updateBackMarkers();
};
/**
/**
* Adds the selection and cursor.
* @param {Range} orientedRange A range containing a cursor
* @returns {Range}
@ -368,7 +368,7 @@ var Editor = require("./editor").Editor;
return orientedRange;
};
/**
/**
* Removes the selection marker.
* @param {Range} The selection range added with [[Editor.addSelectionMarker `addSelectionMarker()`]].
* @method Editor.removeSelectionMarker
@ -455,12 +455,12 @@ var Editor = require("./editor").Editor;
e.preventDefault();
};
/**
/**
* Executes a command for each selection range.
* @param {String} cmd The command to execute
* @param {String} args Any arguments for the command
* @method Editor.forEachSelection
**/
**/
this.forEachSelection = function(cmd, args) {
if (this.inVirtualSelectionMode)
return;
@ -491,7 +491,7 @@ var Editor = require("./editor").Editor;
this.onSelectionChange();
};
/**
/**
* Removes all the selections except the last added one.
* @method Editor.exitMultiSelectMode
**/
@ -522,7 +522,7 @@ var Editor = require("./editor").Editor;
if (this.$readOnly)
return;
this._emit("paste", text);
this._signal("paste", text);
if (!this.inMultiSelectMode || this.inVirtualSelectionMode)
return this.insert(text);
@ -547,7 +547,7 @@ var Editor = require("./editor").Editor;
* @param {Object} The search options
* @param {Boolean} keeps
*
* @returns {Number} The cumulative count of all found matches
* @returns {Number} The cumulative count of all found matches
* @method Editor.findAll
**/
this.findAll = function(needle, options, additive) {
@ -575,11 +575,11 @@ var Editor = require("./editor").Editor;
/**
* Adds a cursor above or below the active cursor.
*
*
* @param {Number} dir The direction of lines to select: -1 for up, 1 for down
* @param {Boolean} skip If `true`, removes the active selection range
*
* @method Editor.selectMoreLines
* @method Editor.selectMoreLines
*/
this.selectMoreLines = function(dir, skip) {
var range = this.selection.toOrientedRange();
@ -619,7 +619,7 @@ var Editor = require("./editor").Editor;
this.selection.substractPoint(toRemove);
};
/**
/**
* Transposes the selected ranges.
* @param {Number} dir The direction to rotate selections
* @method Editor.transposeSelections
@ -661,7 +661,7 @@ var Editor = require("./editor").Editor;
}
};
/**
/**
* Finds the next occurence of text in an active selection and adds it to the selections.
* @param {Number} dir The direction of lines to select: -1 for up, 1 for down
* @param {Boolean} skip If `true`, removes the active selection range
@ -688,7 +688,7 @@ var Editor = require("./editor").Editor;
this.multiSelect.substractPoint(range.cursor);
};
/**
/**
* Aligns the cursors or selected text.
* @method Editor.alignCursors
**/

View file

@ -465,8 +465,16 @@ var VirtualRenderer = function(container, theme) {
};
this.$updateGutterLineHighlight = function() {
this.$gutterLineHighlight.style.top = this.$cursorLayer.$pixelPos.top - this.layerConfig.offset + "px";
this.$gutterLineHighlight.style.height = this.layerConfig.lineHeight + "px";
var pos = this.$cursorLayer.$pixelPos;
var height = this.layerConfig.lineHeight;
if (this.session.getUseWrapMode()) {
var cursor = this.session.selection.getCursor();
cursor.column = 0;
pos = this.$cursorLayer.getPixelPosition(cursor, true);
height *= this.session.getRowLength(cursor.row);
}
this.$gutterLineHighlight.style.top = pos.top - this.layerConfig.offset + "px";
this.$gutterLineHighlight.style.height = height + "px";
};
this.$updatePrintMargin = function() {
@ -632,6 +640,7 @@ var VirtualRenderer = function(container, theme) {
if (!force && (!changes || !this.session || !this.container.offsetWidth))
return;
this._signal("beforeRender");
// text, scrolling and resize changes can cause the view port size to change
if (changes & this.CHANGE_FULL ||
changes & this.CHANGE_SIZE ||
@ -666,12 +675,12 @@ var VirtualRenderer = function(container, theme) {
this.$cursorLayer.update(this.layerConfig);
this.$moveTextAreaToCursor();
this.$highlightGutterLine && this.$updateGutterLineHighlight();
this._signal("afterRender");
return;
}
// scrolling
if (changes & this.CHANGE_SCROLL) {
this.$updateScrollBar();
if (changes & this.CHANGE_TEXT || changes & this.CHANGE_LINES)
this.$textLayer.update(this.layerConfig);
else
@ -682,8 +691,10 @@ var VirtualRenderer = function(container, theme) {
this.$markerBack.update(this.layerConfig);
this.$markerFront.update(this.layerConfig);
this.$cursorLayer.update(this.layerConfig);
this.$moveTextAreaToCursor();
this.$highlightGutterLine && this.$updateGutterLineHighlight();
this.$moveTextAreaToCursor();
this.$updateScrollBar();
this._signal("afterRender");
return;
}
@ -717,6 +728,8 @@ var VirtualRenderer = function(container, theme) {
if (changes & this.CHANGE_SIZE)
this.$updateScrollBar();
this._signal("afterRender");
};
this.$computeLayerConfig = function() {
@ -1122,9 +1135,9 @@ var VirtualRenderer = function(container, theme) {
* @returns {Boolean}
**/
this.isScrollableBy = function(deltaX, deltaY) {
if (deltaY < 0 && this.session.getScrollTop() > 0)
if (deltaY < 0 && this.session.getScrollTop() >= 1)
return true;
if (deltaY > 0 && this.session.getScrollTop() + this.$size.scrollerHeight < this.layerConfig.maxHeight)
if (deltaY > 0 && this.session.getScrollTop() + this.$size.scrollerHeight - this.layerConfig.maxHeight < -1)
return true;
// todo: handle horizontal scrolling
};
@ -1231,10 +1244,6 @@ var VirtualRenderer = function(container, theme) {
this.$composition = null;
};
this._loadTheme = function(name, callback) {
};
/**
* [Sets a new theme for the editor. `theme` should exist, and be a directory path, like `ace/theme/textmate`.]{: #VirtualRenderer.setTheme}
* @param {String} theme The path to a theme