Merge branch 'multiCursor'

Conflicts:
	demo/kitchen-sink/demo.js
	lib/ace/test/all_browser.js
This commit is contained in:
Fabian Jakobs 2012-04-12 16:03:24 +02:00
commit aed7ca654c
26 changed files with 1902 additions and 386 deletions

View file

@ -56,6 +56,10 @@ var HashHandler = require("ace/keyboard/hash_handler").HashHandler;
var modesByName;
// workers do not work for file:
if (location.protocol == "file:")
EditSession.prototype.$useWorker = false;
var Doc = function(name, desc, file) {
this.name = name;
this.desc = desc;
@ -82,6 +86,7 @@ Mode.prototype.supportsFile = function(filename) {
return filename.match(this.extRe);
};
//{
var modes = [
new Mode("c_cpp", "C/C++", ["c", "cpp", "cxx", "h", "hpp"]),
new Mode("clojure", "Clojure", ["clj"]),
@ -260,6 +265,9 @@ var docs = [
)
];
//}
var docsByName = {};
docs.forEach(function(d) {
docsByName[d.name] = d;
@ -324,6 +332,11 @@ modes.forEach(function(mode) {
modeEl.appendChild(option);
});
bindDropdown("mode", function(value) {
env.editor.getSession().setMode(modesByName[value].mode || modesByName.text.mode);
env.editor.getSession().modeName = value;
});
bindDropdown("doc", function(value) {
var doc = docsByName[value].doc;
@ -344,40 +357,43 @@ function updateUIEditorOptions() {
var editor = env.editor;
var session = editor.session;
docEl.value = session.name;
modeEl.value = session.modeName || "text";
session.setFoldStyle(foldingEl.value);
if (!session.getUseWrapMode()) {
wrapModeEl.value = "off";
} else {
wrapModeEl.value = session.getWrapLimitRange().min || "free";
}
saveOption(docEl, session.name);
saveOption(modeEl, session.modeName || "text");
saveOption(wrapModeEl, session.getUseWrapMode() ? session.getWrapLimitRange().min || "free" : "off");
selectStyleEl.checked = editor.getSelectionStyle() == "line";
themeEl.value = editor.getTheme();
highlightActiveEl.checked = editor.getHighlightActiveLine();
showHiddenEl.checked = editor.getShowInvisibles();
showGutterEl.checked = editor.renderer.getShowGutter();
showPrintMarginEl.checked = editor.renderer.getShowPrintMargin();
highlightSelectedWordE.checked = editor.getHighlightSelectedWord();
showHScrollEl.checked = editor.renderer.getHScrollBarAlwaysVisible();
animateScrollEl.checked = editor.getAnimatedScroll();
softTabEl.checked = session.getUseSoftTabs();
behavioursEl.checked = editor.getBehavioursEnabled();
saveOption(selectStyleEl, editor.getSelectionStyle() == "line");
saveOption(themeEl, editor.getTheme());
saveOption(highlightActiveEl, editor.getHighlightActiveLine());
saveOption(showHiddenEl, editor.getShowInvisibles());
saveOption(showGutterEl, editor.renderer.getShowGutter());
saveOption(showPrintMarginEl, editor.renderer.getShowPrintMargin());
saveOption(highlightSelectedWordE, editor.getHighlightSelectedWord());
saveOption(showHScrollEl, editor.renderer.getHScrollBarAlwaysVisible());
saveOption(showHScrollEl, editor.getAnimatedScroll());
saveOption(softTabEl, session.getUseSoftTabs());
saveOption(behavioursEl, editor.getBehavioursEnabled());
}
bindDropdown("mode", function(value) {
env.editor.getSession().setMode(modesByName[value].mode || modesByName.text.mode);
env.editor.getSession().modeName = value;
});
function saveOption(el, val) {
if (!el.onchange || el.onclick)
return;
if ("checked" in el) {
if (val !== undefined)
el.checked = val;
localStorage && localStorage.setItem(el.id, el.checked ? 1 : 0);
} else {
if (val !== undefined)
el.value = val;
localStorage && localStorage.setItem(el.id, el.value);
}
}
bindDropdown("theme", function(value) {
if (!value)
return;
env.editor.setTheme(value);
env.editor.setTheme(value);
});
bindDropdown("keybinding", function(value) {
@ -459,6 +475,7 @@ bindCheckbox("enable_behaviours", function(checked) {
env.editor.setBehavioursEnabled(checked);
});
var secondSession = null;
bindDropdown("split", function(value) {
var sp = env.split;
@ -486,8 +503,12 @@ bindDropdown("split", function(value) {
function bindCheckbox(id, callback) {
var el = document.getElementById(id);
if (localStorage && localStorage.getItem(id))
el.checked = localStorage.getItem(id) == "1";
var onCheck = function() {
callback(!!el.checked);
saveOption(el);
};
el.onclick = onCheck;
onCheck();
@ -495,9 +516,14 @@ function bindCheckbox(id, callback) {
function bindDropdown(id, callback) {
var el = document.getElementById(id);
if (localStorage && localStorage.getItem(id))
el.value = localStorage.getItem(id);
var onChange = function() {
callback(el.value);
saveOption(el);
};
el.onchange = onChange;
onChange();
}
@ -589,4 +615,7 @@ commands.addCommand({
}
});
// add multiple cursor support to editor
require("ace/multi_select").MultiSelect(env.editor);
});

View file

@ -1,4 +1,4 @@
function foo (items, nada) {
function foo(items, nada) {
for (var i=0; i<items.length; i++) {
alert(items[i] + "juhu\n");
} // Real Tab.

@ -1 +1 @@
Subproject commit fadf80529983183e3e4bf01f90e014c1c16f223b
Subproject commit d2a65d0addc2e5ab922bbff9cb6022a4652b4f13

View file

@ -1,131 +1,27 @@
define(function(require, exports, module) {
"use strict";
var keyUtil = require("../lib/keys");
var oop = require("../lib/oop");
var HashHandler = require("../keyboard/hash_handler").HashHandler;
var EventEmitter = require("../lib/event_emitter").EventEmitter;
var CommandManager = function(platform, commands) {
if (typeof platform !== "string")
throw new TypeError("'platform' argument must be either 'mac' or 'win'");
this.platform = platform;
this.commands = {};
this.commmandKeyBinding = {};
if (commands)
commands.forEach(this.addCommand, this);
this.addCommands(commands);
this.setDefaultHandler("exec", function(e) {
e.command.exec(e.editor, e.args || {});
});
};
oop.inherits(CommandManager, HashHandler);
(function() {
this.addCommand = function(command) {
if (this.commands[command.name])
this.removeCommand(command);
this.commands[command.name] = command;
if (command.bindKey) {
this._buildKeyHash(command);
}
};
this.removeCommand = function(command) {
var name = (typeof command === 'string' ? command : command.name);
command = this.commands[name];
delete this.commands[name];
// exaustive search is brute force but since removeCommand is
// not a performance critical operation this should be OK
var ckb = this.commmandKeyBinding;
for (var hashId in ckb) {
for (var key in ckb[hashId]) {
if (ckb[hashId][key] == command)
delete ckb[hashId][key];
}
}
};
this.addCommands = function(commands) {
Object.keys(commands).forEach(function(name) {
var command = commands[name];
if (typeof command === "string")
return this.bindKey(command, name);
if (typeof command === "function")
command = { exec: command };
if (!command.name)
command.name = name;
this.addCommand(command);
}, this);
};
this.removeCommands = function(commands) {
Object.keys(commands).forEach(function(name) {
this.removeCommand(commands[name]);
}, this);
};
this.bindKey = function(key, command) {
if(!key)
return;
var ckb = this.commmandKeyBinding;
key.split("|").forEach(function(keyPart) {
var binding = parseKeys(keyPart, command);
var hashId = binding.hashId;
(ckb[hashId] || (ckb[hashId] = {}))[binding.key] = command;
});
};
this.bindKeys = function(keyList) {
Object.keys(keyList).forEach(function(key) {
this.bindKey(key, keyList[key]);
}, this);
};
this._buildKeyHash = function(command) {
var binding = command.bindKey;
if (!binding)
return;
var key = typeof binding == "string" ? binding: binding[this.platform];
this.bindKey(key, command);
};
function parseKeys(keys, val, ret) {
var key;
var hashId = 0;
var parts = splitSafe(keys);
for (var i=0, l = parts.length; i < l; i++) {
if (keyUtil.KEY_MODS[parts[i]])
hashId = hashId | keyUtil.KEY_MODS[parts[i]];
else
key = parts[i] || "-"; //when empty, the splitSafe removed a '-'
}
return {
key: key,
hashId: hashId
};
}
function splitSafe(s) {
return (s.toLowerCase()
.trim()
.split(new RegExp("[\\s ]*\\-[\\s ]*", "g"), 999));
}
this.findKeyCommand = function findKeyCommand(hashId, textOrKey) {
// Convert keyCode to the string representation.
if (typeof textOrKey == "number") {
textOrKey = keyUtil.keyCodeToString(textOrKey);
}
var ckbr = this.commmandKeyBinding;
return ckbr[hashId] && ckbr[hashId][textOrKey.toLowerCase()];
};
oop.implement(this, EventEmitter);
this.exec = function(command, editor, args) {
if (typeof command === 'string')
@ -137,7 +33,7 @@ var CommandManager = function(platform, commands) {
if (editor && editor.$readOnly && !command.readOnly)
return false;
command.exec(editor, args || {});
this._emit("exec", {editor: editor, command: command, args: args});
return true;
};
@ -146,20 +42,22 @@ var CommandManager = function(platform, commands) {
return;
if (this.recording) {
this.macro.pop();
this.exec = this.normal_exec;
this.removeEventListener("exec", this.$addCommandToMacro);
if (!this.macro.length)
this.macro = this.oldMacro;
return this.recording = false;
}
if (!this.$addCommandToMacro) {
this.$addCommandToMacro = function(e) {
this.macro.push([e.command, e.args]);
}.bind(this);
}
this.oldMacro = this.macro;
this.macro = [];
this.normal_exec = this.exec;
this.exec = function(command, editor, args) {
this.macro.push([command, args]);
return this.normal_exec(command, editor, args);
};
this.on("exec", this.$addCommandToMacro);
return this.recording = true;
};

View file

@ -0,0 +1,121 @@
/* vim:ts=4:sts=4:sw=4:
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ajax.org Code Editor (ACE).
*
* The Initial Developer of the Original Code is
* Ajax.org B.V.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Harutyun Amirjanyan <amirjanyan AT gmail DOT com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
// add multiSelectAction annotations to default commands
require("./default_commands").commands.forEach(function(command) {
var single = RegExp(["selectall"].join("|"), "");
var mapOver = RegExp(["backspace", "del",
"golinedown", "golineup", "gotoend", "gotoleft", "gotolineend", "gotolinestart",
"gotoright", "gotostart", "gotowordleft", "gotowordright",
"indent", "insertstring", "inserttext", "jumptomatching", "outdent",
"removetolineend", "removetolinestart", "removewordleft", "removewordright", "removeline",
"selectdown", "selectleft", "selectlineend", "selectlinestart", "selectright",
"selecttoend", "selecttolineend", "selecttolinestart", "selecttostart",
"selectup", "selectwordleft", "selectwordright",
"splitline", "tolowercase", "touppercase"].join("|"), "");
if (single.test(command.name))
command.multiSelectAction = "single";
else if (mapOver.test(command.name))
command.multiSelectAction = "forEach";
else if (command.name == "transposeletters")
command.multiSelectAction = function(editor) {editor.transposeSelections(1); };
});
// commands to enter multiselect mode
exports.defaultCommands = [{
name: "addCursorAbove",
exec: function(editor) { editor.selectMoreLines(-1); },
bindKey: {win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up"},
readonly: true
}, {
name: "addCursorBelow",
exec: function(editor) { editor.selectMoreLines(1); },
bindKey: {win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down"},
readonly: true
}, {
name: "addCursorAboveSkipCurrent",
exec: function(editor) { editor.selectMoreLines(-1, true); },
bindKey: {win: "Ctrl-Alt-Shift-Up", mac: "Ctrl-Alt-Shift-Up"},
readonly: true
}, {
name: "addCursorBelowSkipCurrent",
exec: function(editor) { editor.selectMoreLines(1, true); },
bindKey: {win: "Ctrl-Alt-Shift-Down", mac: "Ctrl-Alt-Shift-Down"},
readonly: true
}, {
name: "selectMoreBefore",
exec: function(editor) { editor.selectMore(-1); },
bindKey: {win: "Ctrl-Alt-Left", mac: "Ctrl-Alt-Left"},
readonly: true
}, {
name: "selectMoreAfter",
exec: function(editor) { editor.selectMore(1); },
bindKey: {win: "Ctrl-Alt-Right", mac: "Ctrl-Alt-Right"},
readonly: true
}, {
name: "selectNextBefore",
exec: function(editor) { editor.selectMore(-1, true); },
bindKey: {win: "Ctrl-Alt-Shift-Left", mac: "Ctrl-Alt-Shift-Left"},
readonly: true
}, {
name: "selectNextAfter",
exec: function(editor) { editor.selectMore(1, true); },
bindKey: {win: "Ctrl-Alt-Shift-Right", mac: "Ctrl-Alt-Shift-Right"},
readonly: true
}, {
name: "splitIntoLines",
exec: function(editor) { editor.multiSelect.splitIntoLines(); },
bindKey: {win: "Ctrl-Shift-L", mac: "Ctrl-Shift-L"},
readonly: true
}];
// commands active in multiselect mode
exports.multiEditCommands = [{
name: "singleSelection",
bindKey: "esc",
exec: function(editor) { editor.exitMultiSelectMode(); },
readonly: true
}];
var HashHandler = require("../keyboard/hash_handler").HashHandler;
exports.keyboardHandler = new HashHandler(exports.multiEditCommands);
});

View file

@ -162,12 +162,12 @@
.ace_marker-layer .ace_selection {
position: absolute;
z-index: 4;
z-index: 5;
}
.ace_marker-layer .ace_bracket {
position: absolute;
z-index: 5;
z-index: 6;
}
.ace_marker-layer .ace_active_line {
@ -181,7 +181,7 @@
.ace_marker-layer .ace_selected_word {
position: absolute;
z-index: 6;
z-index: 4;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;

View file

@ -1443,8 +1443,8 @@ var EditSession = function(text, mode) {
}
this.getScreenLastRowColumn = function(screenRow) {
//return this.screenToDocumentColumn(screenRow, Number.MAX_VALUE / 10)
return this.documentToScreenColumn(screenRow, this.doc.getLine(screenRow).length);
var pos = this.screenToDocumentPosition(screenRow, Number.MAX_VALUE)
return this.documentToScreenColumn(pos.row, pos.column);
};
this.getDocumentLastRowColumn = function(docRow, docColumn) {
@ -1559,16 +1559,10 @@ var EditSession = function(text, mode) {
docColumn += this.$getStringScreenWidth(line, screenColumn)[1];
// Need to do some clamping action here.
if (this.$useWrapMode) {
if (docColumn >= column) {
// We remove one character at the end such that the docColumn
// position returned is not associated to the next row on the
// screen.
docColumn = column - 1;
}
} else {
docColumn = Math.min(docColumn, line.length);
// We remove one character at the end so that the docColumn
// position returned is not associated to the next row on the screen.
if (this.$useWrapMode && docColumn >= column) {
docColumn = column - 1;
}
if (foldLine) {

View file

@ -76,7 +76,7 @@ var Fold = exports.Fold = function(range, placeholder) {
};
this.addSubFold = function(fold) {
if (this.range.isEequal(fold))
if (this.range.isEqual(fold))
return this;
if (!this.range.containsRange(fold))

View file

@ -714,7 +714,7 @@ function Folding() {
// sometimes singleline folds can be missed by the code above
if (!range.isMultiLine()) {
fold = this.getFoldAt(range.start.row, range.start.column, 1);
if (fold && range.isEequal(fold.range)) {
if (fold && range.isEqual(fold.range)) {
this.removeFold(fold);
return;
}

View file

@ -41,76 +41,126 @@ define(function(require, exports, module) {
var keyUtil = require("../lib/keys");
function HashHandler(config) {
this.setConfig(config);
}
function HashHandler(config, platform) {
this.platform = platform;
this.commands = {};
this.commmandKeyBinding = {};
this.addCommands(config);
};
(function() {
function splitSafe(s, separator, limit, bLowerCase) {
return (bLowerCase && s.toLowerCase() || s)
.replace(/(?:^\s+|\n|\s+$)/g, "")
.split(new RegExp("[\\s ]*" + separator + "[\\s ]*", "g"), limit || 999);
}
this.addCommand = function(command) {
if (this.commands[command.name])
this.removeCommand(command);
this.commands[command.name] = command;
if (command.bindKey) {
this._buildKeyHash(command);
}
};
this.removeCommand = function(command) {
var name = (typeof command === 'string' ? command : command.name);
command = this.commands[name];
delete this.commands[name];
// exhaustive search is brute force but since removeCommand is
// not a performance critical operation this should be OK
var ckb = this.commmandKeyBinding;
for (var hashId in ckb) {
for (var key in ckb[hashId]) {
if (ckb[hashId][key] == command)
delete ckb[hashId][key];
}
}
};
this.addCommands = function(commands) {
commands && Object.keys(commands).forEach(function(name) {
var command = commands[name];
if (typeof command === "string")
return this.bindKey(command, name);
if (typeof command === "function")
command = { exec: command };
if (!command.name)
command.name = name;
this.addCommand(command);
}, this);
};
this.removeCommands = function(commands) {
Object.keys(commands).forEach(function(name) {
this.removeCommand(commands[name]);
}, this);
};
this.bindKey = function(key, command) {
if(!key)
return;
var ckb = this.commmandKeyBinding;
key.split("|").forEach(function(keyPart) {
var binding = parseKeys(keyPart, command);
var hashId = binding.hashId;
(ckb[hashId] || (ckb[hashId] = {}))[binding.key] = command;
});
};
this.bindKeys = function(keyList) {
Object.keys(keyList).forEach(function(key) {
this.bindKey(key, keyList[key]);
}, this);
};
this._buildKeyHash = function(command) {
var binding = command.bindKey;
if (!binding)
return;
var key = typeof binding == "string" ? binding: binding[this.platform];
this.bindKey(key, command);
};
function parseKeys(keys, val, ret) {
var key,
hashId = 0,
parts = splitSafe(keys, "\\-", null, true),
i = 0,
l = parts.length;
var key;
var hashId = 0;
var parts = splitSafe(keys.toLowerCase());
for (; i < l; ++i) {
for (var i = 0, l = parts.length; i < l; i++) {
if (keyUtil.KEY_MODS[parts[i]])
hashId = hashId | keyUtil.KEY_MODS[parts[i]];
else
key = parts[i] || "-"; //when empty, the splitSafe removed a '-'
}
(ret[hashId] || (ret[hashId] = {}))[key] = val;
return ret;
return {
key: key,
hashId: hashId
};
}
function objectReverse(obj, keySplit) {
var i, j, l, key,
ret = {};
for (i in obj) {
key = obj[i];
if (keySplit && typeof key == "string") {
key = key.split(keySplit);
for (j = 0, l = key.length; j < l; ++j)
parseKeys.call(this, key[j], i, ret);
}
else {
parseKeys.call(this, key, i, ret);
}
}
return ret;
function splitSafe(s) {
return (s.trim()
.split(new RegExp("[\\s ]*\\-[\\s ]*", "g"), 999));
}
this.setConfig = function(config) {
this.$config = config;
if (typeof this.$config.reverse == "undefined")
this.$config.reverse = objectReverse.call(this, this.$config, "|");
this.findKeyCommand = function findKeyCommand(hashId, keyString) {
var ckbr = this.commmandKeyBinding;
return ckbr[hashId] && ckbr[hashId][keyString.toLowerCase()];
}
this.handleKeyboard = function(data, hashId, keyString, keyCode) {
return {
command: this.findKeyCommand(hashId, keyString)
};
};
/**
* This function is called by keyBinding.
*/
this.handleKeyboard = function(data, hashId, textOrKey, keyCode) {
// Figure out if a commandKey was pressed or just some text was insert.
if (hashId != 0 || keyCode != 0) {
return {
command: (this.$config.reverse[hashId] || {})[textOrKey]
}
} else {
return {
command: "inserttext",
args: {
text: textOrKey
}
}
}
}
}).call(HashHandler.prototype)
exports.HashHandler = HashHandler;

View file

@ -47,11 +47,10 @@ var Cursor = function(parentEl) {
this.element.className = "ace_layer ace_cursor-layer";
parentEl.appendChild(this.element);
this.cursor = dom.createElement("div");
this.cursor.className = "ace_cursor ace_hidden";
this.element.appendChild(this.cursor);
this.isVisible = false;
this.cursors = [];
this.cursor = this.addCursor();
};
(function() {
@ -65,30 +64,54 @@ var Cursor = function(parentEl) {
this.session = session;
};
this.addCursor = function() {
var el = dom.createElement("div");
var className = "ace_cursor";
if (!this.isVisible)
className += " ace_hidden";
if (this.overwrite)
className += " ace_overwrite";
el.className = className;
this.element.appendChild(el);
this.cursors.push(el);
return el;
};
this.removeCursor = function() {
if (this.cursors.length > 1) {
var el = this.cursors.pop();
el.parentNode.removeChild(el);
return el;
}
};
this.hideCursor = function() {
this.isVisible = false;
dom.addCssClass(this.cursor, "ace_hidden");
for (var i = this.cursors.length; i--; )
dom.addCssClass(this.cursors[i], "ace_hidden");
clearInterval(this.blinkId);
};
this.showCursor = function() {
this.isVisible = true;
dom.removeCssClass(this.cursor, "ace_hidden");
this.cursor.style.visibility = "visible";
for (var i = this.cursors.length; i--; )
dom.removeCssClass(this.cursors[i], "ace_hidden");
this.element.style.visibility = "";
this.restartTimer();
};
this.restartTimer = function() {
clearInterval(this.blinkId);
if (!this.isVisible) {
if (!this.isVisible)
return;
}
var cursor = this.cursor;
var element = this.element;
this.blinkId = setInterval(function() {
cursor.style.visibility = "hidden";
element.style.visibility = "hidden";
setTimeout(function() {
cursor.style.visibility = "visible";
element.style.visibility = "visible";
}, 400);
}, 1000);
};
@ -118,25 +141,53 @@ var Cursor = function(parentEl) {
this.update = function(config) {
this.config = config;
this.pixelPos = this.getPixelPosition(null, true);
if (this.session.selectionMarkerCount > 1) {
var selections = this.session.$selectionMarkers;
var i = 0, sel, cursorIndex = 0;
this.cursor.style.left = this.pixelPos.left + "px";
this.cursor.style.top = this.pixelPos.top + "px";
this.cursor.style.width = config.characterWidth + "px";
this.cursor.style.height = config.lineHeight + "px";
for (var i = selections.length; i--; ) {
sel = selections[i];
var pixelPos = this.getPixelPosition(sel.cursor, true);
var overwrite = this.session.getOverwrite()
if (overwrite != this.overwrite) {
this.overwrite = overwrite;
if (overwrite)
dom.addCssClass(this.cursor, "ace_overwrite");
else
dom.removeCssClass(this.cursor, "ace_overwrite");
var style = (this.cursors[cursorIndex++] || this.addCursor()).style;
style.left = pixelPos.left + "px";
style.top = pixelPos.top + "px";
style.width = config.characterWidth + "px";
style.height = config.lineHeight + "px";
}
if (cursorIndex > 1)
while (this.cursors.length > cursorIndex)
this.removeCursor();
} else {
var pixelPos = this.getPixelPosition(null, true);
var style = this.cursor.style;
style.left = pixelPos.left + "px";
style.top = pixelPos.top + "px";
style.width = config.characterWidth + "px";
style.height = config.lineHeight + "px";
while (this.cursors.length > 1)
this.removeCursor();
}
var overwrite = this.session.getOverwrite();
if (overwrite != this.overwrite)
this.$setOverite(overwrite);
this.restartTimer();
};
this.$setOverite = function(overwrite) {
this.overwrite = overwrite;
for (var i = this.cursors.length; i--; ) {
if (overwrite)
dom.addCssClass(this.cursors[i], "ace_overwrite");
else
dom.removeCssClass(this.cursors[i], "ace_overwrite");
}
};
this.destroy = function() {
clearInterval(this.blinkId);
}

View file

@ -112,7 +112,7 @@ var Marker = function(parentEl) {
};
/**
* Draws a marker, which spans a range of text in a single line
* Draws a marker, which spans a range of text on multiple lines
*/
this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) {
// selection start
@ -187,7 +187,7 @@ var Marker = function(parentEl) {
};
/**
* Draws a marker which covers one single full line
* Draws a marker which covers part or whole width of a single screen line
*/
this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig, extraLength, type) {
var padding = type === "background" ? 0 : this.$padding;

View file

@ -302,16 +302,16 @@ var Text = function(parentEl) {
};
this.$renderLinesFragment = function(config, firstRow, lastRow) {
var fragment = this.element.ownerDocument.createDocumentFragment(),
row = firstRow,
fold = this.session.getNextFoldLine(row),
foldStart = fold ?fold.start.row :Infinity;
var fragment = this.element.ownerDocument.createDocumentFragment();
var row = firstRow;
var foldLine = this.session.getNextFoldLine(row);
var foldStart = foldLine ? foldLine.start.row : Infinity;
while (true) {
if (row > foldStart) {
row = fold.end.row+1;
fold = this.session.getNextFoldLine(row, fold);
foldStart = fold ?fold.start.row :Infinity;
row = foldLine.end.row+1;
foldLine = this.session.getNextFoldLine(row, foldLine);
foldStart = foldLine ? foldLine.start.row : Infinity;
}
if (row > lastRow)
break;
@ -350,15 +350,15 @@ var Text = function(parentEl) {
var html = [];
var firstRow = config.firstRow, lastRow = config.lastRow;
var row = firstRow,
fold = this.session.getNextFoldLine(row),
foldStart = fold ?fold.start.row :Infinity;
var row = firstRow;
var foldLine = this.session.getNextFoldLine(row);
var foldStart = foldLine ? foldLine.start.row : Infinity;
while (true) {
if (row > foldStart) {
row = fold.end.row+1;
fold = this.session.getNextFoldLine(row, fold);
foldStart = fold ?fold.start.row :Infinity;
row = foldLine.end.row+1;
foldLine = this.session.getNextFoldLine(row, foldLine);
foldStart = foldLine ? foldLine.start.row :Infinity;
}
if (row > lastRow)
break;

View file

@ -134,7 +134,7 @@ function DefaultHandlers(editor) {
if (!editor.$mouseHandler.$clickSelection) {
if (!dragCursor) {
editor.moveCursorToPosition(pos);
editor.selection.clearSelection(pos.row, pos.column);
editor.selection.clearSelection();
}
}
@ -169,7 +169,6 @@ function DefaultHandlers(editor) {
if (distance > DRAG_OFFSET) {
state = STATE_SELECT;
var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY);
cursor.row = Math.max(0, Math.min(cursor.row, editor.session.getLength()-1));
onStartSelect(cursor);
}
else if ((time - mousedownTime) > editor.getDragDelay()) {
@ -196,7 +195,7 @@ function DefaultHandlers(editor) {
else {
if (!_self.$clickSelection) {
editor.moveCursorToPosition(pos);
editor.selection.clearSelection(pos.row, pos.column);
editor.selection.clearSelection();
}
}
state = STATE_SELECT;
@ -205,7 +204,6 @@ function DefaultHandlers(editor) {
var onUpdateSelectionInterval = function() {
var anchor;
var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY);
cursor.row = Math.max(0, Math.min(cursor.row, editor.session.getLength()-1));
if (_self.$clickSelection) {
if (_self.$clickSelection.contains(cursor.row, cursor.column)) {
@ -231,8 +229,6 @@ function DefaultHandlers(editor) {
var onDragSelectionInterval = function() {
dragCursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY);
dragCursor.row = Math.max(0, Math.min(dragCursor.row, editor.session.getLength() - 1));
editor.moveCursorToPosition(dragCursor);
};

View file

@ -45,7 +45,7 @@ function FoldHandler(editor) {
var position = e.getDocumentPosition();
var session = editor.session;
// If the user dclicked on a fold, then expand it.
// If the user clicked on a fold, then expand it.
var fold = session.getFoldAt(position.row, position.column, 1);
if (fold) {
if (e.getAccelKey())

View file

@ -90,7 +90,6 @@ var MouseEvent = exports.MouseEvent = function(domEvent, editor) {
var pageX = event.getDocumentX(this.domEvent);
var pageY = event.getDocumentY(this.domEvent);
this.$pos = this.editor.renderer.screenToTextCoordinates(pageX, pageY);
this.$pos.row = Math.max(0, Math.min(this.$pos.row, this.editor.session.getLength()-1));
return this.$pos;
};

View file

@ -0,0 +1,164 @@
/* vim:ts=4:sts=4:sw=4:
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ajax.org Code Editor (ACE).
*
* The Initial Developer of the Original Code is
* Ajax.org B.V.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Harutyun Amirjanyan <amirjanyan AT gmail DOT com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
var event = require("../lib/event");
// mouse
function isSamePoint(p1, p2) {
return p1.row == p2.row && p1.column == p2.column
}
function onMouseDown(e) {
var ev = e.domEvent;
var alt = ev.altKey;
var shift = ev.shiftKey;
var ctrl = e.getAccelKey();
var button = e.getButton();
if (!ctrl && !alt) {
if (e.editor.inMultiSelectMode) {
if (button == 0) {
e.editor.exitMultiSelectMode();
} else if (button == 2) {
var editor = e.editor;
var selectionEmpty = editor.selection.isEmpty();
editor.textInput.onContextMenu({x: e.clientX, y: e.clientY}, selectionEmpty);
event.capture(editor.container, function(){}, editor.textInput.onContextMenuClose);
e.stop();
}
}
return;
}
var editor = e.editor;
var selection = editor.selection;
var isMultiSelect = editor.inMultiSelectMode;
var pos = e.getDocumentPosition();
var cursor = selection.getCursor()
var inSelection = e.inSelection() || (selection.isEmpty() && isSamePoint(pos, cursor));
var mouseX = e.pageX, mouseY = e.pageY;
var clickX = mouseX, clickY = mouseY;
var onMouseSelection = function(e) {
mouseX = event.getDocumentX(e);
mouseY = event.getDocumentY(e);
};
var blockSelect = function() {
var newCursor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY);
var cursor = session.screenToDocumentPosition(newCursor.row, newCursor.column);
if (isSamePoint(screenCursor, newCursor)
&& isSamePoint(cursor, selection.selectionLead))
return;
screenCursor = newCursor;
editor.selection.moveCursorToPosition(cursor);
editor.selection.clearSelection();
editor.renderer.scrollCursorIntoView();
editor.removeSelectionMarkers(rectSel);
rectSel = selection.rectangularRangeBlock(screenCursor, screenAnchor);
rectSel.forEach(editor.addSelectionMarker, editor);
editor.updateSelectionMarkers()
};
var session = editor.session;
var anchor = selection.getCursor();
var screenAnchor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY);
var clippedAnchor = session.documentToScreenPosition(anchor);
var screenCursor = screenAnchor;
if (ctrl && !shift && !alt && button == 0) {
if (!isMultiSelect && inSelection)
return // dragging
if (!isMultiSelect)
selection.addRange(selection.toOrientedRange());
var oldRange = selection.rangeList.rangeAtPoint(pos);
event.capture(editor.container, function(){}, function() {
var tmpSel = selection.toOrientedRange();
if (oldRange && tmpSel.isEmpty() && isSamePoint(oldRange.cursor, tmpSel.cursor))
selection.substractPoint(tmpSel.cursor);
else
selection.addRange(tmpSel);
});
} else if (!shift && alt && button == 0) {
e.stop()
if (isMultiSelect && !ctrl)
selection.toSingleRange();
else if (!isMultiSelect && ctrl)
selection.addRange();
selection.moveCursorToPosition(pos);
selection.clearSelection();
var rectSel = [];
var onMouseSelectionEnd = function(e) {
clearInterval(timerId);
editor.removeSelectionMarkers(rectSel);
for (var i = 0; i < rectSel.length; i++)
selection.addRange(rectSel[i])
};
onSelectionInterval = blockSelect;
event.capture(editor.container, onMouseSelection, onMouseSelectionEnd);
var timerId = setInterval(function() {onSelectionInterval()}, 20);
return e.preventDefault();
}
}
exports.onMouseDown = onMouseDown;
});

581
lib/ace/multi_select.js Normal file
View file

@ -0,0 +1,581 @@
/* vim:ts=4:sts=4:sw=4:
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ajax.org Code Editor (ACE).
*
* The Initial Developer of the Original Code is
* Ajax.org B.V.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Harutyun Amirjanyan <amirjanyan AT gmail DOT com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
var RangeList = require("./range_list").RangeList;
var Range = require("./range").Range;
var Selection = require("./selection").Selection;
var onMouseDown = require("./mouse/multi_select_handler").onMouseDown;
exports.commands = require("./commands/multi_select_commands");
// Todo: session.find or editor.findVolatile that returns range
var Search = require("./search").Search;
var search = new Search();
function find(session, needle, dir) {
search.$options.wrap = true;
search.$options.needle = needle;
search.$options.backwards = dir == -1;
return search.find(session);
}
// extend EditSession
var EditSession = require("./edit_session").EditSession;
;(function() {
this.getSelectionMarkers = function() {
return this.$selectionMarkers;
};
}).call(EditSession.prototype);
// extend Selection
;(function() {
// list of ranges in reverse addition order
this.ranges = null;
// automatically sorted list of ranges
this.rangeList = null;
/**
* add a range to selection entering multiselect mode if necessary
*/
this.addRange = function(range) {
if (!this.inMultiSelectMode && this.rangeCount == 0) {
var oldRange = this.toOrientedRange();
if (!range || !range.isEqual(oldRange)) {
this.rangeList.add(oldRange);
this.$onAddRange(oldRange);
}
}
if (!range)
return;
if (!range.cursor)
range.cursor = range.end;
var removed = this.rangeList.add(range);
this.$onAddRange(range);
if (removed.length)
this.$onRemoveRange(removed);
if (this.rangeCount > 0 && !this.inMultiSelectMode) {
this._emit("multiSelect");
this.inMultiSelectMode = true;
this.session.$undoSelect = false;
this.rangeList.attach(this.session);
}
};
this.toSingleRange = function(range) {
range = range || this.ranges[0];
var removed = this.rangeList.removeAll();
if (removed.length)
this.$onRemoveRange(removed);
range && this.fromOrientedRange(range);
};
this.substractPoint = function(pos) {
var removed = this.rangeList.substractPoint(pos);
if (removed) {
this.$onRemoveRange(removed);
return removed[0]
}
};
this.mergeOverlappingRanges = function() {
var removed = this.rangeList.merge();
if (removed.length)
this.$onRemoveRange(removed);
else if(this.ranges[0])
this.fromOrientedRange(this.ranges[0]);
};
this.$onAddRange = function(range) {
this.rangeCount = this.rangeList.ranges.length;
this.ranges.unshift(range);
this.fromOrientedRange(range);
this._emit("addRange", {range: range});
};
this.$onRemoveRange = function(removed) {
this.rangeCount = this.rangeList.ranges.length;
if (this.rangeCount == 1 && this.inMultiSelectMode) {
var lastRange = this.rangeList.ranges.pop()
removed.push(lastRange);
this.rangeCount = 0;
}
for (var i = removed.length; i--; ) {
var index = this.ranges.indexOf(removed[i]);
this.ranges.splice(index, 1);
}
this._emit("removeRange", {ranges: removed});
if (this.rangeCount == 0 && this.inMultiSelectMode) {
this.inMultiSelectMode = false;
this._emit("singleSelect");
this.session.$undoSelect = true;
this.rangeList.detach(this.session);
}
lastRange = lastRange || this.ranges[0];
if (lastRange && !lastRange.isEqual(this.getRange()))
this.fromOrientedRange(lastRange);
};
// adds multicursor support to selection
this.$initRangeList = function() {
if (this.rangeList)
return;
this.rangeList = new RangeList();
this.ranges = [];
this.rangeCount = 0;
};
this.getAllRanges = function() {
return this.rangeList.ranges.concat();
};
this.splitIntoLines = function () {
if (this.rangeCount > 1) {
var ranges = this.rangeList.ranges;
var lastRange = ranges[ranges.length - 1];
var range = Range.fromPoints(ranges[0].start, lastRange.end);
this.toSingleRange();
this.setSelectionRange(range, lastRange.cursor == lastRange.start);
} else {
var cursor = this.session.documentToScreenPosition(this.selectionLead);
var anchor = this.session.documentToScreenPosition(this.selectionAnchor);
var rectSel = this.rectangularRangeBlock(cursor, anchor);
rectSel.forEach(this.addRange, this);
}
};
/**
* gets list of ranges composing rectangular block on the screen
* @includeEmptyLines if true includes ranges inside the block which
* are empty becuase of the clipping
*/
this.rectangularRangeBlock = function(screenCursor, screenAnchor, includeEmptyLines) {
var rectSel = [];
var xBackwards = screenCursor.column < screenAnchor.column;
if (xBackwards) {
var startColumn = screenCursor.column;
var endColumn = screenAnchor.column;
} else {
var startColumn = screenAnchor.column;
var endColumn = screenCursor.column;
}
var yBackwards = screenCursor.row < screenAnchor.row;
if (yBackwards) {
var startRow = screenCursor.row;
var endRow = screenAnchor.row;
} else {
var startRow = screenAnchor.row;
var endRow = screenCursor.row;
}
if (startColumn < 0)
startColumn = 0;
if (startRow < 0)
startRow = 0;
if (startRow == endRow)
includeEmptyLines = true;
for (var row = startRow; row <= endRow; row++) {
var range = Range.fromPoints(
this.session.screenToDocumentPosition(row, startColumn),
this.session.screenToDocumentPosition(row, endColumn)
);
if (range.isEmpty()) {
if (docEnd && isSamePoint(range.end, docEnd))
break;
var docEnd = range.end;
}
range.cursor = xBackwards ? range.start : range.end;
rectSel.push(range);
}
if (yBackwards)
rectSel.reverse();
if (!includeEmptyLines) {
var end = rectSel.length - 1;
while (rectSel[end].isEmpty() && end > 0)
end--;
if (end > 0) {
var start = 0;
while (rectSel[start].isEmpty())
start++;
}
for (var i = end; i >= start; i--) {
if (rectSel[i].isEmpty())
rectSel.splice(i, 1);
}
}
return rectSel;
};
}).call(Selection.prototype);
// extend Editor
var Editor = require("./editor").Editor;
;(function() {
this.updateSelectionMarkers = function() {
this.renderer.updateCursor();
this.renderer.updateBackMarkers();
};
this.addSelectionMarker = function(orientedRange) {
if (!orientedRange.cursor)
orientedRange.cursor = orientedRange.end;
var style = this.getSelectionStyle();
orientedRange.marker = this.session.addMarker(orientedRange, "ace_selection", style);
this.session.$selectionMarkers.push(orientedRange);
this.session.selectionMarkerCount = this.session.$selectionMarkers.length;
return orientedRange;
};
this.removeSelectionMarkers = function(ranges) {
for (var i = ranges.length; i--; ) {
var range = ranges[i];
if (!range.marker)
continue;
this.session.removeMarker(range.marker);
var index = this.session.$selectionMarkers.indexOf(range);
if (index != -1)
this.session.$selectionMarkers.splice(index, 1)
}
this.session.selectionMarkerCount = this.session.$selectionMarkers.length;
};
this.$onAddRange = function(e) {
this.addSelectionMarker(e.range);
this.renderer.updateCursor();
this.renderer.updateBackMarkers();
};
this.$onRemoveRange = function(e) {
this.removeSelectionMarkers(e.ranges);
this.renderer.updateCursor();
this.renderer.updateBackMarkers();
};
this.$onMultiSelect = function(e) {
if (this.inMultiSelectMode)
return;
this.inMultiSelectMode = true;
this.setStyle("multiselect");
this.keyBinding.addKeyboardHandler(exports.commands.keyboardHandler);
this.commands.on("exec", this.$onMultiSelectExec);
this.renderer.updateCursor();
this.renderer.updateBackMarkers();
};
this.$onSingleSelect = function(e) {
if (this.session.multiSelect.inVirtualMode)
return;
this.inMultiSelectMode = false;
this.unsetStyle("multiselect");
this.keyBinding.removeKeyboardHandler(exports.commands.keyboardHandler);
this.commands.removeEventListener("exec", this.$onMultiSelectExec);
this.renderer.updateCursor();
this.renderer.updateBackMarkers();
};
this.$onMultiSelectExec = function(e) {
var command = e.command;
var editor = e.editor;
if (!command.multiSelectAction) {
command.exec(editor, e.args || {});
editor.multiSelect.addRange(editor.multiSelect.toOrientedRange());
editor.multiSelect.mergeOverlappingRanges();
} else if (command.multiSelectAction == "forEach") {
editor.forEachSelection(command, e.args);
} else if (command.multiSelectAction == "single") {
editor.exitMultiSelectMode();
command.exec(editor, e.args || {});
} else {
command.multiSelectAction(editor, e.args || {});
}
e.preventDefault();
};
this.forEachSelection = function(cmd, args) {
if (this.inVirtualSelectionMode)
return;
var session = this.session;
var selection = this.selection;
var rangeList = selection.rangeList;
var reg = selection._eventRegistry;
selection._eventRegistry = {};
var tmpSel = new Selection(session);
this.inVirtualSelectionMode = true;
for (var i = rangeList.ranges.length; i--;) {
tmpSel.fromOrientedRange(rangeList.ranges[i]);
this.selection = session.selection = tmpSel;
cmd.exec(this, args || {});
tmpSel.toOrientedRange(rangeList.ranges[i]);
}
tmpSel.detach();
this.selection = session.selection = selection;
this.inVirtualSelectionMode = false;
selection._eventRegistry = reg;
selection.mergeOverlappingRanges();
this.onCursorChange();
this.onSelectionChange();
};
this.exitMultiSelectMode = function() {
if (this.inVirtualSelectionMode)
return;
this.multiSelect.toSingleRange();
};
// todo route copy/cut/paste through commandmanager
this.getCopyText = function() {
var text = "";
if (this.inMultiSelectMode) {
var ranges = this.multiSelect.rangeList.ranges;
text = [];
for (var i = 0; i < ranges.length; i++) {
text.push(this.session.getTextRange(ranges[i]));
}
text = text.join(this.session.getDocument().getNewLineCharacter());
} else if (!this.selection.isEmpty()) {
text = this.session.getTextRange(this.getSelectionRange());
}
return text;
};
this.onCut = function() {
var cmd = {
name: "cut",
exec: function(editor) {
var range = editor.getSelectionRange();
editor._emit("cut", range);
if (!editor.selection.isEmpty()) {
editor.session.remove(range);
editor.clearSelection();
}
},
readonly: true,
multiSelectAction: "forEach"
};
this.commands.exec(cmd, this);
};
// commands
this.selectMoreLines = function(dir, skip) {
var range = this.selection.toOrientedRange();
var isBackwards = range.cursor == range.end;
var screenLead = this.session.documentToScreenPosition(range.cursor);
if (this.selection.$desiredColumn)
screenLead.column = this.selection.$desiredColumn;
var lead = this.session.screenToDocumentPosition(screenLead.row + dir, screenLead.column);
if (!range.isEmpty()) {
var screenAnchor = this.session.documentToScreenPosition(isBackwards ? range.end : range.start);
var anchor = this.session.screenToDocumentPosition(screenAnchor.row + dir, screenAnchor.column);
} else {
var anchor = lead;
}
if (isBackwards) {
var newRange = Range.fromPoints(lead, anchor);
newRange.cursor = newRange.start;
} else {
var newRange = Range.fromPoints(anchor, lead);
newRange.cursor = newRange.end;
}
newRange.desiredColumn = screenLead.column;
if (!this.selection.inMultiSelectMode) {
this.selection.addRange(range);
} else {
var allRanges = this.selection.rangeList.ranges;
// remove range if at end
// var atEdge = range.isEqual(allRanges[dir == 1 ? 0 : allRanges.length - 1]);
if (skip)
var toRemove = range.cursor;
}
this.selection.addRange(newRange);
if (toRemove)
this.selection.substractPoint(toRemove);
}
this.transposeSelections = function(dir) {
var session = this.session;
var sel = session.multiSelect;
var all = sel.ranges;
var words = [];
for (var i = all.length; i--; ) {
var range = all[i];
if (range.isEmpty()) {
var tmp = session.getWordRange(range.start.row, range.start.column);
range.start.row = tmp.start.row;
range.start.column = tmp.start.column;
range.end.row = tmp.end.row;
range.end.column = tmp.end.column;
}
words.unshift(this.session.getTextRange(range));
}
if (dir < 0)
words.unshift(words.pop());
else
words.push(words.shift());
for (var i = all.length; i--; ) {
var range = all[i];
var tmp = range.clone();
session.replace(range, words[i]);
range.start.row = tmp.start.row;
range.start.column = tmp.start.column;
}
}
this.selectMore = function (dir, skip) {
var session = this.session;
var sel = session.multiSelect;
var range = sel.toOrientedRange();
if (range.isEmpty()) {
var range = session.getWordRange(range.start.row, range.start.column);
range.cursor = range.end;
this.multiSelect.addRange(range);
}
var needle = session.getTextRange(range);
var newRange = find(session, needle, dir);
if (newRange) {
newRange.cursor = dir == -1 ? newRange.start : newRange.end;
this.multiSelect.addRange(newRange);
}
if (skip)
this.multiSelect.substractPoint(range.cursor);
}
}).call(Editor.prototype);
function isSamePoint(p1, p2) {
return p1.row == p2.row && p1.column == p2.column
}
// patch
// adds multicursor support to a session
exports.onSessionChange = function(e) {
var session = e.session;
if (!session.multiSelect) {
session.$selectionMarkers = [];
session.selection.$initRangeList();
session.multiSelect = session.selection;
}
this.multiSelect = session.multiSelect;
var oldSession = e.oldSession;
if (oldSession) {
// todo use events
if (oldSession.multiSelect && oldSession.multiSelect.editor == this)
oldSession.multiSelect.editor = null;
session.multiSelect.removeEventListener("addRange", this.$onAddRange);
session.multiSelect.removeEventListener("removeRange", this.$onRemoveRange);
session.multiSelect.removeEventListener("multiSelect", this.$onMultiSelect);
session.multiSelect.removeEventListener("singleSelect", this.$onSingleSelect);
}
session.multiSelect.on("addRange", this.$onAddRange);
session.multiSelect.on("removeRange", this.$onRemoveRange);
session.multiSelect.on("multiSelect", this.$onMultiSelect);
session.multiSelect.on("singleSelect", this.$onSingleSelect);
// this.$onSelectionChange = this.onSelectionChange.bind(this);
if (this.inMultiSelectMode != session.selection.inMultiSelectMode) {
if (session.selection.inMultiSelectMode)
this.$onMultiSelect();
else
this.$onSingleSelect();
}
};
// adds multicursor support to editor instance
function MultiSelect(editor) {
editor.$onAddRange = editor.$onAddRange.bind(editor);
editor.$onRemoveRange = editor.$onRemoveRange.bind(editor);
editor.$onMultiSelect = editor.$onMultiSelect.bind(editor);
editor.$onSingleSelect = editor.$onSingleSelect.bind(editor);
exports.onSessionChange.call(editor, editor);
editor.on("changeSession", exports.onSessionChange.bind(editor));
editor.on("mousedown", onMouseDown);
editor.commands.addCommands(exports.commands.defaultCommands);
}
exports.MultiSelect = MultiSelect;
});

View file

@ -0,0 +1,144 @@
/* vim:ts=4:sts=4:sw=4:
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ajax.org Code Editor (ACE).
*
* The Initial Developer of the Original Code is
* Ajax.org B.V.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Harutyun Amirjanyan <amirjanyan AT gmail DOT com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var EditSession = require("./edit_session").EditSession;
var Editor = require("./editor").Editor;
var MockRenderer = require("./test/mockrenderer").MockRenderer;
var Range = require("./range").Range;
var assert = require("./test/assertions");
var MultiSelect = require("./multi_select").MultiSelect;
var editor
var exec = function(name, times, args) {
do {
editor.commands.exec(name, editor, args);
} while(times --> 1)
};
var testRanges = function(str) {
assert.equal(editor.selection.getAllRanges()+"", str);
}
module.exports = {
name: "ACE multi_select.js",
"test: multiselect editing": function() {
var doc = new EditSession([
"w1.w2",
" wtt.w",
" wtt.w"
]);
editor = new Editor(new MockRenderer(), doc);
MultiSelect(editor);
editor.navigateFileEnd();
exec("selectMoreBefore", 3);
assert.ok(editor.inMultiSelectMode);
assert.equal(editor.selection.getAllRanges().length, 4);
assert.equal(editor.getCopyText(), "wwww");
exec("insertstring", 1, "a");
exec("backspace", 2);
assert.equal(editor.session.getValue(), "w1.w2\ntt\ntt");
assert.equal(editor.selection.getAllRanges().length, 4);
exec("selectall");
assert.ok(!editor.inMultiSelectMode);
//assert.equal(editor.selection.getAllRanges().length, 1);
},
"test: multiselect navigation": function() {
var doc = new EditSession([
"w1.w2",
" wtt.w",
" wtt.we"
]);
editor = new Editor(new MockRenderer(), doc);
MultiSelect(editor);
editor.selectMoreLines(1);
testRanges("Range: [0/0] -> [0/0],Range: [1/0] -> [1/0]");
assert.ok(editor.inMultiSelectMode);
exec("golinedown");
exec("gotolineend");
testRanges("Range: [1/9] -> [1/9],Range: [2/10] -> [2/10]");
exec("selectwordleft");
testRanges("Range: [1/8] -> [1/9],Range: [2/8] -> [2/10]");
exec("golinedown", 2);
assert.ok(!editor.inMultiSelectMode);
},
"test: multiselect session change": function() {
var doc = new EditSession([
"w1.w2",
" wtt.w",
" wtt.w"
]);
var editor = new Editor(new MockRenderer(), doc);
MultiSelect(editor);
editor.selectMoreLines(1)
assert.equal(
editor.selection.getAllRanges()+"",
"Range: [0/0] -> [0/0],Range: [1/0] -> [1/0]"
);
assert.ok(editor.inMultiSelectMode);
var doc2 = new EditSession(["w1"]);
editor.setSession(doc2);
assert.ok(!editor.inMultiSelectMode);
editor.setSession(doc);
assert.ok(editor.inMultiSelectMode);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}

View file

@ -51,7 +51,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
};
(function() {
this.isEequal = function(range) {
this.isEqual = function(range) {
return this.start.row == range.start.row &&
this.end.row == range.end.row &&
this.start.column == range.start.column &&
@ -117,6 +117,11 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0;
}
this.intersectsRange = function(range) {
var cmp = this.compareRange(range);
return (cmp == -1 || cmp == 0 || cmp == 1);
}
this.isEnd = function(row, column) {
return this.end.row == row && this.end.column == column;
}
@ -276,6 +281,21 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
return Range.fromPoints(start || this.start, end || this.end);
};
this.fixOrientation = function() {
if (
this.start.row < this.end.row
|| (this.start.row == this.end.row && this.start.column < this.end.column)
) {
return false;
}
var temp = this.start;
this.end = this.start;
this.start = temp;
return true;
};
this.isEmpty = function() {
return (this.start.row == this.end.row && this.start.column == this.end.column);
};

233
lib/ace/range_list.js Normal file
View file

@ -0,0 +1,233 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ajax.org Code Editor (ACE).
*
* The Initial Developer of the Original Code is
* Ajax.org B.V.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Harutyun Amirjanyan <amirjanyan AT gmail DOT com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
"use strict";
var oop = require("./lib/oop");
var EventEmitter = require("./lib/event_emitter").EventEmitter;
var RangeList = function(startRow, startColumn, endRow, endColumn) {
this.ranges = [];
};
(function() {
this.comparePoints = function(p1, p2) {
return p1.row - p2.row || p1.column - p2.column
};
this.pointIndex = function(pos, startIndex) {
var list = this.ranges;
for (var i = startIndex || 0; i < list.length; i++) {
var range = list[i];
var cmp = this.comparePoints(pos, range.end);
if (cmp > 0)
continue;
if (cmp == 0)
return i;
cmp = this.comparePoints(pos, range.start);
if (cmp >= 0)
return i;
return -i-1;
}
return -i - 1
};
this.add = function(range) {
var startIndex = this.pointIndex(range.start);
if (startIndex < 0)
startIndex = -startIndex - 1;
var endIndex = this.pointIndex(range.end, startIndex);
if (endIndex < 0)
endIndex = -endIndex - 1;
else
endIndex++;
return this.ranges.splice(startIndex, endIndex - startIndex, range);
};
this.addList = function(list) {
var removed = [];
for (var i = list.length; i--; ) {
removed.push.call(removed, this.add(list[i]))
}
return removed;
};
this.substractPoint = function(pos) {
var i = this.pointIndex(pos);
if (i >= 0)
return this.ranges.splice(i, 1);
};
// merge overlapping ranges
this.merge = function() {
var removed = [];
var list = this.ranges;
var next = list[0], range;
for (var i = 1; i < list.length; i++) {
range = next;
next = list[i];
var cmp = this.comparePoints(range.end, next.start)
if (cmp < 0)
continue;
if (cmp == 0 && !(range.isEmpty() || next.isEmpty()))
continue;
if (this.comparePoints(range.end, next.end) < 0) {
range.end.row = next.end.row;
range.end.column = next.end.column;
}
list.splice(i, 1);
removed.push(next);
next = range;
i--;
}
return removed;
};
this.contains = function(row, column) {
return this.pointIndex({row: row, column: column}) >= 0;
};
this.containsPoint = function(pos) {
return this.pointIndex(pos) >= 0;
};
this.rangeAtPoint = function(pos) {
var i = this.pointIndex(pos);
if (i >= 0)
return this.ranges[i];
};
this.clipRows = function(startRow, endRow) {
var list = this.ranges;
if (list[0].start.row > endRow || list[list.length - 1].start.row < startRow)
return [];
var startIndex = this.pointIndex({row: startRow, column: 0});
if (startIndex < 0)
startIndex = -startIndex - 1;
var endIndex = this.pointIndex({row: endRow, column: 0}, startIndex);
if (endIndex < 0)
endIndex = -endIndex - 1;
var clipped = [];
for (var i = startIndex; i < endIndex; i++) {
clipped.push(list[i]);
}
return clipped;
};
this.removeAll = function() {
return this.ranges.splice(0, this.ranges.length);
};
this.attach = function(session) {
if (this.session)
this.detach();
this.session = session;
this.onChange = this.$onChange.bind(this);
this.session.on('change', this.onChange);
};
this.detach = function() {
if (!this.session)
return;
this.session.removeListener('change', this.onChange)
this.session = null
};
this.$onChange = function(e) {
var changeRange = e.data.range;
if (e.data.action[0] == "i"){
var start = changeRange.start;
var end = changeRange.end;
} else {
var end = changeRange.start;
var start = changeRange.end;
}
var startRow = start.row;
var endRow = end.row;
var lineDif = endRow - startRow;
var colDiff = -start.column + end.column;
var ranges = this.ranges;
for (var i=0, n = ranges.length; i < n; i++) {
var r = ranges[i];
if (r.end.row < startRow)
continue;
if (r.start.row > startRow)
break;
if (r.start.row == startRow && r.start.column >= start.column ) {
r.start.column += colDiff;
r.start.row += lineDif;
}
if (r.end.row == startRow && r.end.column >= start.column) {
r.end.column += colDiff;
r.end.row += lineDif;
}
}
if (lineDif != 0 && i < n) {
for (; i < n; i++) {
var r = ranges[i];
r.start.row += lineDif;
r.end.row += lineDif;
}
}
};
}).call(RangeList.prototype);
exports.RangeList = RangeList;
});

170
lib/ace/range_list_test.js Normal file
View file

@ -0,0 +1,170 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ajax.org Code Editor (ACE).
*
* The Initial Developer of the Original Code is
* Ajax.org B.V.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var Range = require("./range").Range;
var RangeList = require("./range_list").RangeList;
var EditSession = require("./edit_session").EditSession;
var assert = require("./test/assertions");
function flatten(rangeList) {
var points = [];
rangeList.ranges.forEach(function(r) {
points.push(r.start.row, r.start.column, r.end.row, r.end.column)
})
return points;
}
function testRangeList(rangeList, points) {
assert.equal("" + flatten(rangeList), "" + points);
}
module.exports = {
name: "ACE range_list.js",
"test: rangeList pointIndex": function() {
var rangeList = new RangeList();
rangeList.ranges = [
new Range(1,2,3,4),
new Range(4,2,5,4),
new Range(8,8,9,9)
];
assert.equal(rangeList.pointIndex({row: 0, column: 1}), -1);
assert.equal(rangeList.pointIndex({row: 1, column: 2}), 0);
assert.equal(rangeList.pointIndex({row: 1, column: 3}), 0);
assert.equal(rangeList.pointIndex({row: 3, column: 4}), 0);
assert.equal(rangeList.pointIndex({row: 4, column: 1}), -2);
assert.equal(rangeList.pointIndex({row: 5, column: 1}), 1);
assert.equal(rangeList.pointIndex({row: 8, column: 9}), 2);
assert.equal(rangeList.pointIndex({row: 18, column: 9}), -4);
},
"test: rangeList add": function() {
var rangeList = new RangeList();
rangeList.addList([
new Range(9,0,9,1),
new Range(1,2,3,4),
new Range(8,8,9,9),
new Range(4,2,5,4),
new Range(3,20,3,24),
new Range(6,6,7,7)
]);
assert.equal(rangeList.ranges.length, 5);
rangeList.add(new Range(1,2,3,5));
assert.range(rangeList.ranges[0], 1,2,3,5);
assert.equal(rangeList.ranges.length, 5);
rangeList.add(new Range(7,7,7,7));
assert.range(rangeList.ranges[3], 7,7,7,7);
rangeList.add(new Range(7,8,7,8));
assert.range(rangeList.ranges[4], 7,8,7,8);
},
"test: rangeList add empty": function() {
var rangeList = new RangeList();
rangeList.addList([
new Range(7,10,7,10),
new Range(9,10,9,10),
new Range(8,10,8,10)
]);
assert.equal(rangeList.ranges.length, 3);
rangeList.add(new Range(9,10,9,10));
testRangeList(rangeList, [7,10,7,10,8,10,8,10,9,10,9,10]);
},
"test: rangeList merge": function() {
var rangeList = new RangeList();
rangeList.addList([
new Range(1,2,3,4),
new Range(4,2,5,4),
new Range(6,6,7,7),
new Range(8,8,9,9)
]);
var removed = [];
assert.equal(rangeList.ranges.length, 4);
rangeList.ranges[1].end.row = 7;
removed = rangeList.merge();
assert.equal(removed.length, 1);
assert.range(rangeList.ranges[1], 4,2,7,7);
assert.equal(rangeList.ranges.length, 3);
rangeList.ranges[0].end.row = 10;
removed = rangeList.merge();
assert.range(rangeList.ranges[0], 1,2,10,4);
assert.equal(removed.length, 2);
assert.equal(rangeList.ranges.length, 1);
rangeList.ranges.push(new Range(10,10,10,10));
rangeList.ranges.push(new Range(10,10,10,10));
removed = rangeList.merge();
assert.equal(rangeList.ranges.length, 2);
},
"test: rangeList remove": function() {
var rangeList = new RangeList();
var list = [
new Range(1,2,3,4),
new Range(4,2,5,4),
new Range(6,6,7,7),
new Range(8,8,9,9)
];
rangeList.addList(list);
assert.equal(rangeList.ranges.length, 4);
rangeList.substractPoint({row: 1, column: 2});
assert.equal(rangeList.ranges.length, 3);
rangeList.substractPoint({row: 6, column: 7});
assert.equal(rangeList.ranges.length, 2);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}

View file

@ -59,18 +59,18 @@ var Selection = function(session) {
this.selectionLead = this.doc.createAnchor(0, 0);
this.selectionAnchor = this.doc.createAnchor(0, 0);
var _self = this;
var self = this;
this.selectionLead.on("change", function(e) {
_self._emit("changeCursor");
if (!_self.$isEmpty)
_self._emit("changeSelection");
if (!_self.$preventUpdateDesiredColumnOnChange && e.old.column != e.value.column)
_self.$updateDesiredColumn();
self._emit("changeCursor");
if (!self.$isEmpty)
self._emit("changeSelection");
if (!self.$keepDesiredColumnOnChange && e.old.column != e.value.column)
self.$desiredColumn = null;
});
this.selectionAnchor.on("change", function() {
if (!_self.$isEmpty)
_self._emit("changeSelection");
if (!self.$isEmpty)
self._emit("changeSelection");
});
};
@ -180,12 +180,7 @@ var Selection = function(session) {
this.setSelectionAnchor(range.start.row, range.start.column);
this.selectTo(range.end.row, range.end.column);
}
this.$updateDesiredColumn();
};
this.$updateDesiredColumn = function() {
var cursor = this.getCursor();
this.$desiredColumn = this.session.documentToScreenColumn(cursor.row, cursor.column);
this.$desiredColumn = null;
};
this.$moveSelection = function(mover) {
@ -392,14 +387,14 @@ var Selection = function(session) {
this.moveCursorTo(fold.end.row, fold.end.column);
return;
}
// first skip space
if (match = this.session.nonTokenRe.exec(rightOfCursor)) {
column += this.session.nonTokenRe.lastIndex;
this.session.nonTokenRe.lastIndex = 0;
rightOfCursor = line.substring(column);
}
// if at line end proceed with next line
if (column >= line.length) {
this.moveCursorTo(row, line.length);
@ -408,7 +403,7 @@ var Selection = function(session) {
this.moveCursorWordRight();
return;
}
// advance to the end of the next token
if (match = this.session.tokenRe.exec(rightOfCursor)) {
column += this.session.tokenRe.lastIndex;
@ -433,19 +428,19 @@ var Selection = function(session) {
if (str == null) {
str = this.doc.getLine(row).substring(0, column)
}
var leftOfCursor = lang.stringReverse(str);
var match;
this.session.nonTokenRe.lastIndex = 0;
this.session.tokenRe.lastIndex = 0;
// skip whitespace
if (match = this.session.nonTokenRe.exec(leftOfCursor)) {
column -= this.session.nonTokenRe.lastIndex;
leftOfCursor = leftOfCursor.slice(this.session.nonTokenRe.lastIndex);
this.session.nonTokenRe.lastIndex = 0;
}
// if at begin of the line proceed in line above
if (column <= 0) {
this.moveCursorTo(row, 0);
@ -470,8 +465,14 @@ var Selection = function(session) {
this.selectionLead.column
);
var screenCol = (chars === 0 && this.$desiredColumn) || screenPos.column;
var docPos = this.session.screenToDocumentPosition(screenPos.row + rows, screenCol);
if (chars === 0) {
if (this.$desiredColumn)
screenPos.column = this.$desiredColumn;
else
this.$desiredColumn = screenPos.column;
}
var docPos = this.session.screenToDocumentPosition(screenPos.row + rows, screenPos.column);
// move the cursor and update the desired column
this.moveCursorTo(docPos.row, docPos.column + chars, chars === 0);
@ -481,7 +482,7 @@ var Selection = function(session) {
this.moveCursorTo(position.row, position.column);
};
this.moveCursorTo = function(row, column, preventUpdateDesiredColumn) {
this.moveCursorTo = function(row, column, keepDesiredColumn) {
// Ensure the row/column is not inside of a fold.
var fold = this.session.getFoldAt(row, column, 1);
if (fold) {
@ -489,21 +490,47 @@ var Selection = function(session) {
column = fold.start.column;
}
this.$preventUpdateDesiredColumnOnChange = true;
this.$keepDesiredColumnOnChange = true;
this.selectionLead.setPosition(row, column);
this.$preventUpdateDesiredColumnOnChange = false;
this.$keepDesiredColumnOnChange = false;
if (!preventUpdateDesiredColumn)
this.$updateDesiredColumn(this.selectionLead.column);
if (!keepDesiredColumn)
this.$desiredColumn = null;
};
this.moveCursorToScreen = function(row, column, preventUpdateDesiredColumn) {
this.moveCursorToScreen = function(row, column, keepDesiredColumn) {
var pos = this.session.screenToDocumentPosition(row, column);
row = pos.row;
column = pos.column;
this.moveCursorTo(row, column, preventUpdateDesiredColumn);
this.moveCursorTo(pos.row, pos.column, keepDesiredColumn);
};
// remove listeners from document
this.detach = function() {
this.selectionLead.detach();
this.selectionAnchor.detach();
this.session = this.doc = null;
}
this.fromOrientedRange = function(range) {
this.setSelectionRange(range, range.cursor == range.start);
this.$desiredColumn = range.desiredColumn || this.$desiredColumn;
}
this.toOrientedRange = function(range) {
var r = this.getRange();
if (range) {
range.start.column = r.start.column;
range.start.row = r.start.row;
range.end.column = r.end.column;
range.end.row = r.end.row;
} else {
range = r;
}
range.cursor = this.isBackwards() ? range.start : range.end;
range.desiredColumn = this.$desiredColumn;
return range;
}
}).call(Selection.prototype);
exports.Selection = Selection;

View file

@ -9,96 +9,116 @@ var passed = 0
var failed = 0
var log = document.getElementById("log")
var tests = [
require("ace/anchor_test"),
require("ace/commands/command_manager_test"),
require("ace/document_test"),
require("ace/edit_session_test"),
require("ace/editor_change_document_test"),
require("ace/editor_highlight_selected_word_test"),
require("ace/editor_navigation_test"),
require("ace/editor_text_edit_test"),
require("ace/ext/static_highlight_test"),
require("ace/layer/text_test"),
require("ace/lib/event_emitter_test"),
require("ace/mode/coffee/parser_test"),
require("ace/mode/coffee_highlight_rules_test"),
require("ace/mode/coldfusion_test"),
require("ace/mode/css_test"),
require("ace/mode/css_highlight_rules_test"),
require("ace/mode/css_worker"),
require("ace/mode/html_test"),
require("ace/mode/html_highlight_rules_test"),
require("ace/mode/javascript_test"),
require("ace/mode/javascript_highlight_rules_test"),
require("ace/mode/javascript_worker_test"),
require("ace/mode/python_test"),
require("ace/mode/ruby_highlight_rules_test"),
require("ace/mode/text_test"),
require("ace/mode/xml_test"),
require("ace/mode/xml_highlight_rules_test"),
require("ace/mode/folding/cstyle_test"),
require("ace/mode/folding/html_test"),
require("ace/mode/folding/pythonic_test"),
require("ace/mode/folding/xml_test"),
require("ace/range_test"),
require("ace/search_test"),
require("ace/selection_test"),
require("ace/token_iterator_test"),
require("ace/virtual_renderer_test")
]
var testNames = [
"ace/anchor_test",
"ace/commands/command_manager_test",
"ace/document_test",
"ace/edit_session_test",
"ace/editor_change_document_test",
"ace/editor_highlight_selected_word_test",
"ace/editor_navigation_test",
"ace/editor_text_edit_test",
"ace/ext/static_highlight_test",
"ace/layer/text_test",
"ace/lib/event_emitter_test",
"ace/mode/coffee/parser_test",
"ace/mode/coffee_highlight_rules_test",
"ace/mode/coldfusion_test",
"ace/mode/css_test",
"ace/mode/css_highlight_rules_test",
"ace/mode/css_worker",
"ace/mode/html_test",
"ace/mode/html_highlight_rules_test",
"ace/mode/javascript_test",
"ace/mode/javascript_highlight_rules_test",
"ace/mode/javascript_worker_test",
"ace/mode/python_test",
"ace/mode/ruby_highlight_rules_test",
"ace/mode/text_test",
"ace/mode/xml_test",
"ace/mode/xml_highlight_rules_test",
"ace/mode/folding/cstyle_test",
"ace/mode/folding/html_test",
"ace/mode/folding/pythonic_test",
"ace/mode/folding/xml_test",
"ace/multi_select_test",
"ace/range_test",
"ace/range_list_test",
"ace/search_test",
"ace/selection_test",
"ace/token_iterator_test",
"ace/virtual_renderer_test"
];
async.list(tests)
.expand(function(test) {
return AsyncTest.testcase(test)
}, AsyncTest.TestGenerator)
.run()
.each(function(test, next) {
var node = document.createElement("div");
node.className = test.passed ? "passed" : "failed";
var html = ["<a href='?'>all tests</a><br>"];
for (var i in testNames) {
var href = testNames[i];
html.push("<a href='?", href, "'>", href.replace(/^ace\//, "") ,"</a><br>");
}
var name = test.name
if (test.suiteName)
name = test.suiteName + ": " + test.name
var nav = document.createElement("div");
nav.innerHTML = html.join("");
nav.style.cssText = "position:absolute;right:0;top:0";
document.body.appendChild(nav);
var msg = "[" + test.count + "/" + test.index + "] " + name + " " + (test.passed ? "OK" : "FAIL")
if (!test.passed) {
if (test.err.stack)
var err = test.err.stack
if (location.search)
testNames = location.search.substr(1).split(",")
require(testNames, function() {
var tests = testNames.map(require);
async.list(tests)
.expand(function(test) {
return AsyncTest.testcase(test)
}, AsyncTest.TestGenerator)
.run()
.each(function(test, next) {
var node = document.createElement("div");
node.className = test.passed ? "passed" : "failed";
var name = test.name
if (test.suiteName)
name = test.suiteName + ": " + test.name
var msg = "[" + test.count + "/" + test.index + "] " + name + " " + (test.passed ? "OK" : "FAIL")
if (!test.passed) {
if (test.err.stack)
var err = test.err.stack
else
var err = test.err
console.error(msg);
console.error(err);
msg += "<pre class='error'>" + err + "</pre>";
} else {
console.log(msg);
}
node.innerHTML = msg;
log.appendChild(node);
next()
})
.each(function(test) {
if (test.passed)
passed += 1
else
var err = test.err
console.error(msg);
console.error(err);
msg += "<pre class='error'>" + err + "</pre>";
} else {
console.log(msg);
}
node.innerHTML = msg;
log.appendChild(node);
next()
})
.each(function(test) {
if (test.passed)
passed += 1
else
failed += 1
})
.end(function() {
log.innerHTML += [
"<div class='summary'>",
"<br>",
"Summary: <br>",
"<br>",
"Total number of tests: " + (passed + failed) + "<br>",
(passed ? "Passed tests: " + passed + "<br>" : ""),
(failed ? "Failed tests: " + failed + "<br>" : "")
].join("")
console.log("Total number of tests: " + (passed + failed));
console.log("Passed tests: " + passed);
console.log("Failed tests: " + failed);
})
failed += 1
})
.end(function() {
log.innerHTML += [
"<div class='summary'>",
"<br>",
"Summary: <br>",
"<br>",
"Total number of tests: " + (passed + failed) + "<br>",
(passed ? "Passed tests: " + passed + "<br>" : ""),
(failed ? "Failed tests: " + failed + "<br>" : "")
].join("")
console.log("Total number of tests: " + (passed + failed));
console.log("Passed tests: " + passed);
console.log("Failed tests: " + failed);
})
});
});

View file

@ -180,6 +180,12 @@ MockRenderer.prototype.visualizeFocus = function() {
MockRenderer.prototype.setAnnotations = function() {
};
MockRenderer.prototype.setStyle = function() {
};
MockRenderer.prototype.unsetStyle = function() {
};
MockRenderer.prototype.textToScreenCoordinates = function() {
return {
pageX: 0,

View file

@ -794,6 +794,19 @@ var VirtualRenderer = function(container, theme) {
// todo: handle horizontal scrolling
};
this.pixelToScreenCoordinates = function(pageX, pageY) {
var canvasPos = this.scroller.getBoundingClientRect();
var col = Math.round(
(pageX + this.scrollLeft - canvasPos.left - this.$padding - dom.getPageScrollLeft()) / this.characterWidth
);
var row = Math.floor(
(pageY + this.scrollTop - canvasPos.top - dom.getPageScrollTop()) / this.lineHeight
);
return {row: row, column: col};
};
this.screenToTextCoordinates = function(pageX, pageY) {
var canvasPos = this.scroller.getBoundingClientRect();
@ -885,7 +898,7 @@ var VirtualRenderer = function(container, theme) {
return afterLoad(module);
_self._loadTheme(moduleName, function() {
require([theme], function(module) {
require([moduleName], function(module) {
if (_self.$themeValue !== theme)
return;