From 7f39633a5e6dfc3e6dd2072da88c89370817d513 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 13 Mar 2012 15:30:48 +0400 Subject: [PATCH 01/33] add a little more text to js doc for testing multicursor --- demo/kitchen-sink/docs/javascript.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/demo/kitchen-sink/docs/javascript.js b/demo/kitchen-sink/docs/javascript.js index ca27af56..51a3da7a 100644 --- a/demo/kitchen-sink/docs/javascript.js +++ b/demo/kitchen-sink/docs/javascript.js @@ -2,4 +2,25 @@ function foo(items) { for (var i=0; i Date: Tue, 13 Mar 2012 22:04:45 +0400 Subject: [PATCH 02/33] add ability to draw show cursors --- lib/ace/layer/cursor.js | 102 ++++++++++++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index 5fa7644a..0fc42e86 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -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,56 @@ var Cursor = function(parentEl) { this.update = function(config) { this.config = config; - this.pixelPos = this.getPixelPosition(null, true); + if (!this.session.selection.multiSelectMode) { + this.pixelPos = this.getPixelPosition(null, true); + var style = this.cursor.style; + style.left = this.pixelPos.left + "px"; + style.top = this.pixelPos.top + "px"; + style.width = config.characterWidth + "px"; + style.height = config.lineHeight + "px"; - 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"; + while (this.cursors.length > 1) + this.removeCursor(); + } else { + var selections = this.session.selection.allRanges + var i = 0, sel, cursorIndex = 0; - 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"); + while ((sel = selections[i]) && sel.cursor.row < config.firstRow) + i++; + + while ((sel = selections[i]) && sel.cursor.row < config.lastRow) { + this.pixelPos = this.getPixelPosition(sel.cursor, true); + + var style = (this.cursors[cursorIndex++] || this.addCursor()).style; + + style.left = this.pixelPos.left + "px"; + style.top = this.pixelPos.top + "px"; + style.width = config.characterWidth + "px"; + style.height = config.lineHeight + "px"; + + i++; + } + while (cursorIndex < this.cursors.length) + 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); } From 6381f3e048506d5f0e2b8b1da81551d6ff1bd9a4 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 13 Mar 2012 22:06:48 +0400 Subject: [PATCH 03/33] make hash_handler more useful --- lib/ace/commands/command_manager.js | 120 +--------------------- lib/ace/keyboard/hash_handler.js | 154 ++++++++++++++++++---------- 2 files changed, 106 insertions(+), 168 deletions(-) diff --git a/lib/ace/commands/command_manager.js b/lib/ace/commands/command_manager.js index 7b24fb45..efa3803c 100644 --- a/lib/ace/commands/command_manager.js +++ b/lib/ace/commands/command_manager.js @@ -1,131 +1,21 @@ 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 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); }; +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, separator) { - 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()]; - } this.exec = function(command, editor, args) { if (typeof command === 'string') diff --git a/lib/ace/keyboard/hash_handler.js b/lib/ace/keyboard/hash_handler.js index 040e8de6..c5598858 100644 --- a/lib/ace/keyboard/hash_handler.js +++ b/lib/ace/keyboard/hash_handler.js @@ -41,76 +41,124 @@ 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; - } - - 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 { + key: key, + hashId: hashId } - return ret; } - this.setConfig = function(config) { - this.$config = config; - if (typeof this.$config.reverse == "undefined") - this.$config.reverse = objectReverse.call(this, this.$config, "|"); + function splitSafe(s, separator) { + return (s.trim() + .split(new RegExp("[\\s ]*\\-[\\s ]*", "g"), 999)); + } + + 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 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; From 1e4828e3d83cf087a028e4a40d98755837c5a00e Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 12 Mar 2012 16:57:33 +0400 Subject: [PATCH 04/33] remove redundant clamping while converting between screen and document positions --- lib/ace/edit_session.js | 14 ++++---------- lib/ace/mouse/default_handlers.js | 4 ---- lib/ace/mouse/mouse_event.js | 1 - 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 6aa21896..43e17372 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1502,16 +1502,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) { diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index e1d912a8..6413db7c 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -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()) { @@ -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); }; diff --git a/lib/ace/mouse/mouse_event.js b/lib/ace/mouse/mouse_event.js index 31c16ae7..8e32f548 100644 --- a/lib/ace/mouse/mouse_event.js +++ b/lib/ace/mouse/mouse_event.js @@ -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; }; From 020cf5f39a0dc6cd40367b1e4600de14a4215a1f Mon Sep 17 00:00:00 2001 From: nightwing Date: Wed, 14 Mar 2012 14:55:56 +0400 Subject: [PATCH 05/33] improve inbrowser test runner --- lib/ace/test/all_browser.js | 192 ++++++++++++++++++++---------------- 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index 90eab310..1553f379 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -9,96 +9,114 @@ 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_tokenizer_test"), - require("ace/mode/coldfusion_test"), - require("ace/mode/css_test"), - require("ace/mode/css_tokenizer_test"), - require("ace/mode/css_worker"), - require("ace/mode/html_test"), - require("ace/mode/html_tokenizer_test"), - require("ace/mode/javascript_test"), - require("ace/mode/javascript_tokenizer_test"), - require("ace/mode/javascript_worker_test"), - require("ace/mode/python_test"), - require("ace/mode/ruby_tokenizer_test"), - require("ace/mode/text_test"), - require("ace/mode/xml_test"), - require("ace/mode/xml_tokenizer_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_tokenizer_test", + "ace/mode/coldfusion_test", + "ace/mode/css_test", + "ace/mode/css_tokenizer_test", + "ace/mode/css_worker", + "ace/mode/html_test", + "ace/mode/html_tokenizer_test", + "ace/mode/javascript_test", + "ace/mode/javascript_tokenizer_test", + "ace/mode/javascript_worker_test", + "ace/mode/python_test", + "ace/mode/ruby_tokenizer_test", + "ace/mode/text_test", + "ace/mode/xml_test", + "ace/mode/xml_tokenizer_test", + "ace/mode/folding/cstyle_test", + "ace/mode/folding/html_test", + "ace/mode/folding/pythonic_test", + "ace/mode/folding/xml_test", + "ace/range_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 = ["all tests
"]; +for (var i in testNames) { + var href = testNames[i]; + html.push("", href.replace(/^ace\//, "") ,"
"); +} - 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 += "
" + err + "
"; + } 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 += "
" + err + "
"; - } 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 += [ - "
", - "
", - "Summary:
", - "
", - "Total number of tests: " + (passed + failed) + "
", - (passed ? "Passed tests: " + passed + "
" : ""), - (failed ? "Failed tests: " + failed + "
" : "") - ].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 += [ + "
", + "
", + "Summary:
", + "
", + "Total number of tests: " + (passed + failed) + "
", + (passed ? "Passed tests: " + passed + "
" : ""), + (failed ? "Failed tests: " + failed + "
" : "") + ].join("") + console.log("Total number of tests: " + (passed + failed)); + console.log("Passed tests: " + passed); + console.log("Failed tests: " + failed); + }) +}); }); From a4f82e70f6a70c4a570d667af808465cbf04b491 Mon Sep 17 00:00:00 2001 From: nightwing Date: Wed, 14 Mar 2012 14:57:58 +0400 Subject: [PATCH 06/33] add draft implementation of rangeList to test it's usefulness --- lib/ace/range_list.js | 142 ++++++++++++++++++++++++++++++++++++ lib/ace/range_list_test.js | 98 +++++++++++++++++++++++++ lib/ace/test/all_browser.js | 1 + 3 files changed, 241 insertions(+) create mode 100644 lib/ace/range_list.js create mode 100644 lib/ace/range_list_test.js diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js new file mode 100644 index 00000000..4d31ffa5 --- /dev/null +++ b/lib/ace/range_list.js @@ -0,0 +1,142 @@ +/* ***** 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 + * + * 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 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-2; + 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++; + + this.ranges.splice(startIndex, endIndex - startIndex, range); + return startIndex; + }; + + this.addList = function(list) { + list.forEach(this.add, this); + }; + + this.substractRange = function(range) { + var i = this.pointIndex(range.start); + if (i > 0) + this.splice(i, 1); + + i = this.pointIndex(range.end); + if (i > 0) + this.splice(i, 1); + }; + + this.substractPoint = function(pos) { + var i = this.pointIndex(pos); + if (i > 0) + this.splice(i, 1); + }; + + this.contains = function(row, column) { + return this.pointIndex({row: row, column: column}) >= 0; + }; + + this.containsRange = function(row, column) { + //todo + }; + + this.containsPoint = function(pos) { + return this.pointIndex(pos) >= 0; + }; + + this.attach = function(session) { + if (this.session) + this.dettach(); + + this.session = session; + this.onChange = this.$onChange.bind(this); + + this.session.on('change', this.onChange); + }; + + this.dettach = function() { + if (!this.session) + return; + this.session.removeListener('change', this.onChange) + this.session = null + }; + + this.$onChange = function(e) { + // todo + }; + +}).call(RangeList.prototype); + +exports.RangeList = RangeList; +}); diff --git a/lib/ace/range_list_test.js b/lib/ace/range_list_test.js new file mode 100644 index 00000000..92500b27 --- /dev/null +++ b/lib/ace/range_list_test.js @@ -0,0 +1,98 @@ +/* ***** 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 + * + * 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"); + +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}), -1); + assert.equal(rangeList.pointIndex({row: 1, column: 3}), 0); + assert.equal(rangeList.pointIndex({row: 3, column: 4}), -2); + 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[4], 7,7,7,7); + } + +}; + +}); + +if (typeof module !== "undefined" && module === require.main) { + require("asyncjs").test.testcase(module.exports).exec() +} \ No newline at end of file diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index 1553f379..b9484a86 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -42,6 +42,7 @@ var testNames = [ "ace/mode/folding/pythonic_test", "ace/mode/folding/xml_test", "ace/range_test", + "ace/range_list_test", "ace/search_test", "ace/selection_test", "ace/token_iterator_test", From 43bfe515ed87124408d82209b90199830459ac81 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 18 Mar 2012 17:16:10 +0400 Subject: [PATCH 07/33] small cleanup --- lib/ace/layer/text.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 6e3509bf..4d533b10 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -264,16 +264,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; @@ -312,15 +312,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; From ce215714c362befff7878899bb87cfcde5c20a05 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 18 Mar 2012 17:20:37 +0400 Subject: [PATCH 08/33] fix multiline text markers and update comments to match actual functions --- lib/ace/edit_session.js | 4 ++-- lib/ace/layer/marker.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 43e17372..48de054f 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1386,8 +1386,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) { diff --git a/lib/ace/layer/marker.js b/lib/ace/layer/marker.js index ea31a2df..ac361249 100644 --- a/lib/ace/layer/marker.js +++ b/lib/ace/layer/marker.js @@ -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; From 37943f8df5b3107d93e5f2bbbaa7e6252a901b57 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 18 Mar 2012 23:18:50 +0400 Subject: [PATCH 09/33] more work on range_list --- lib/ace/range_list.js | 82 ++++++++++++++++++++++++++++---------- lib/ace/range_list_test.js | 34 ++++++++++++++-- 2 files changed, 92 insertions(+), 24 deletions(-) diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js index 4d31ffa5..75885509 100644 --- a/lib/ace/range_list.js +++ b/lib/ace/range_list.js @@ -38,11 +38,16 @@ 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() { + oop.implement(this, EventEmitter); + this.comparePoints = function(p1, p2) { return p1.row - p2.row || p1.column - p2.column }; @@ -57,9 +62,9 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { if (cmp > 0) continue; if (cmp == 0) - return -i-2; + return i; cmp = this.comparePoints(pos, range.start); - if (cmp > 0) + if (cmp >= 0) return i; return -i-1; @@ -79,7 +84,8 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { else endIndex++; - this.ranges.splice(startIndex, endIndex - startIndex, range); + var removed = this.ranges.splice(startIndex, endIndex - startIndex, range); + this._emit("remove", {ranges: removed}); return startIndex; }; @@ -87,34 +93,68 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { list.forEach(this.add, this); }; - this.substractRange = function(range) { - var i = this.pointIndex(range.start); - if (i > 0) - this.splice(i, 1); - - i = this.pointIndex(range.end); - if (i > 0) - this.splice(i, 1); - }; - this.substractPoint = function(pos) { var i = this.pointIndex(pos); - if (i > 0) - this.splice(i, 1); + if (i > 0){ + var removed = this.splice(i, 1); + this._emit("remove", {ranges: removed}); + } + }; + + // 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]; + if (this.comparePoints(range.end, next.start) > 0) { + 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--; + } + } + + if (removed.length) + this._emit("remove", {ranges: removed}); }; this.contains = function(row, column) { - return this.pointIndex({row: row, column: column}) >= 0; - }; - - this.containsRange = function(row, column) { - //todo + return this.pointIndex({row: row, column: column}) >= 0; }; this.containsPoint = function(pos) { - return this.pointIndex(pos) >= 0; + return this.pointIndex(pos) >= 0; }; + + 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.attach = function(session) { if (this.session) this.dettach(); diff --git a/lib/ace/range_list_test.js b/lib/ace/range_list_test.js index 92500b27..7865e6d1 100644 --- a/lib/ace/range_list_test.js +++ b/lib/ace/range_list_test.js @@ -60,9 +60,9 @@ module.exports = { ]; assert.equal(rangeList.pointIndex({row: 0, column: 1}), -1); - assert.equal(rangeList.pointIndex({row: 1, column: 2}), -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}), -2); + 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); @@ -86,7 +86,35 @@ module.exports = { assert.equal(rangeList.ranges.length, 5); rangeList.add(new Range(7,7,7,7)); - assert.range(rangeList.ranges[4], 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 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 = []; + rangeList.on('remove', function(e) {removed = e.ranges}); + + assert.equal(rangeList.ranges.length, 4); + + rangeList.ranges[1].end.row = 7; + 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; + rangeList.merge(); + assert.range(rangeList.ranges[0], 1,2,10,4); + assert.equal(removed.length, 2); + assert.equal(rangeList.ranges.length, 1); } }; From bd6ecd1b8b74cb5870beba7a10ea6ad0a11a587a Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 20 Mar 2012 00:00:38 +0400 Subject: [PATCH 10/33] rearrange markers to bring selected_word behind selection --- lib/ace/css/editor.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 47bd1b42..5ae86c9c 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -158,12 +158,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 { @@ -173,7 +173,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; From 107735560a3e17d7af385d1506c67da1e3902f12 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 20 Mar 2012 00:02:24 +0400 Subject: [PATCH 11/33] do not require cursors to be sorted --- lib/ace/layer/cursor.js | 46 +++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index 0fc42e86..c2a7ca28 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -141,37 +141,33 @@ var Cursor = function(parentEl) { this.update = function(config) { this.config = config; - if (!this.session.selection.multiSelectMode) { - this.pixelPos = this.getPixelPosition(null, true); + if (this.session.selection.rangeCount > 1) { + var selections = this.session.selection.allRanges + var i = 0, sel, cursorIndex = 0; + + for (var i = selections.length; i--; ) { + sel = selections[i]; + var pixelPos = this.getPixelPosition(sel.cursor, true); + + 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"; + } + while (cursorIndex < this.cursors.length) + this.removeCursor(); + } else { + var pixelPos = this.getPixelPosition(null, true); var style = this.cursor.style; - style.left = this.pixelPos.left + "px"; - style.top = this.pixelPos.top + "px"; + 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(); - } else { - var selections = this.session.selection.allRanges - var i = 0, sel, cursorIndex = 0; - - while ((sel = selections[i]) && sel.cursor.row < config.firstRow) - i++; - - while ((sel = selections[i]) && sel.cursor.row < config.lastRow) { - this.pixelPos = this.getPixelPosition(sel.cursor, true); - - var style = (this.cursors[cursorIndex++] || this.addCursor()).style; - - style.left = this.pixelPos.left + "px"; - style.top = this.pixelPos.top + "px"; - style.width = config.characterWidth + "px"; - style.height = config.lineHeight + "px"; - - i++; - } - while (cursorIndex < this.cursors.length) - this.removeCursor(); } var overwrite = this.session.getOverwrite(); From 6ec76596a34a786216ef9d5ceec55fa88d1a9cae Mon Sep 17 00:00:00 2001 From: nightwing Date: Wed, 21 Mar 2012 11:45:27 +0400 Subject: [PATCH 12/33] cleanup --- lib/ace/mouse/default_handlers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index 6413db7c..14439ca2 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -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(); } } @@ -195,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; From eb4a36fe7f35da6961f5bc6961ba9ad5cc8cd6aa Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 22 Mar 2012 19:34:37 +0400 Subject: [PATCH 13/33] fix hash_handler --- lib/ace/keyboard/hash_handler.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ace/keyboard/hash_handler.js b/lib/ace/keyboard/hash_handler.js index c5598858..cc3b43fd 100644 --- a/lib/ace/keyboard/hash_handler.js +++ b/lib/ace/keyboard/hash_handler.js @@ -156,7 +156,9 @@ function HashHandler(config, platform) { } this.handleKeyboard = function(data, hashId, keyString, keyCode) { - return this.findKeyCommand(hashId, keyString) + return { + command: this.findKeyCommand(hashId, keyString) + }; }; }).call(HashHandler.prototype) From c2e1f8815de652e751c9083e8cbe8dfc5d1ed447 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 22 Mar 2012 19:37:18 +0400 Subject: [PATCH 14/33] emit events on add/remove to range_list --- lib/ace/range_list.js | 37 +++++++++++++------- lib/ace/range_list_test.js | 69 +++++++++++++++++++++++++++++++------- 2 files changed, 82 insertions(+), 24 deletions(-) diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js index 75885509..fcf9b250 100644 --- a/lib/ace/range_list.js +++ b/lib/ace/range_list.js @@ -85,7 +85,9 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { endIndex++; var removed = this.ranges.splice(startIndex, endIndex - startIndex, range); - this._emit("remove", {ranges: removed}); + this._emit("add", {range: range}); + if (removed.length) + this._emit("remove", {ranges: removed}); return startIndex; }; @@ -95,9 +97,11 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { this.substractPoint = function(pos) { var i = this.pointIndex(pos); - if (i > 0){ - var removed = this.splice(i, 1); + + if (i >= 0) { + var removed = this.ranges.splice(i, 1); this._emit("remove", {ranges: removed}); + return removed[0] } }; @@ -109,17 +113,22 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { for (var i = 1; i < list.length; i++) { range = next; next = list[i]; - if (this.comparePoints(range.end, next.start) > 0) { - if (this.comparePoints(range.end, next.end) < 0) { - range.end.row = next.end.row; - range.end.column = next.end.column; - } + var cmp = this.comparePoints(range.end, next.start) + if (cmp < 0) + continue; - list.splice(i, 1); - removed.push(next); - next = range; - i--; + 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--; } if (removed.length) @@ -154,6 +163,10 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { return clipped; }; + this.removeAll = function() { + if (this.ranges.length) + this._emit("remove", {ranges: this.ranges.splice(0, this.ranges.length)}); + }; this.attach = function(session) { if (this.session) diff --git a/lib/ace/range_list_test.js b/lib/ace/range_list_test.js index 7865e6d1..a73202f5 100644 --- a/lib/ace/range_list_test.js +++ b/lib/ace/range_list_test.js @@ -47,10 +47,21 @@ 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 = [ @@ -58,7 +69,7 @@ module.exports = { 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); @@ -68,7 +79,7 @@ module.exports = { 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([ @@ -80,17 +91,30 @@ module.exports = { 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([ @@ -101,22 +125,43 @@ module.exports = { ]); var removed = []; rangeList.on('remove', function(e) {removed = e.ranges}); - + assert.equal(rangeList.ranges.length, 4); - + rangeList.ranges[1].end.row = 7; 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; rangeList.merge(); - assert.range(rangeList.ranges[0], 1,2,10,4); + 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)); + 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); } - + }; }); From 580ed00afa440d787b5ecc0303720de5f6501872 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 22 Mar 2012 19:38:31 +0400 Subject: [PATCH 15/33] better handling of multiple cursors --- lib/ace/layer/cursor.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index c2a7ca28..bf6c2901 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -142,7 +142,7 @@ var Cursor = function(parentEl) { this.config = config; if (this.session.selection.rangeCount > 1) { - var selections = this.session.selection.allRanges + var selections = this.session.selection.getAllRanges(); var i = 0, sel, cursorIndex = 0; for (var i = selections.length; i--; ) { @@ -156,8 +156,9 @@ var Cursor = function(parentEl) { style.width = config.characterWidth + "px"; style.height = config.lineHeight + "px"; } - while (cursorIndex < this.cursors.length) - this.removeCursor(); + if (this.cursors.length > 1) + while (cursorIndex < this.cursors.length) + this.removeCursor(); } else { var pixelPos = this.getPixelPosition(null, true); var style = this.cursor.style; From c955e33e04640d32530b342e51813e35d9357a0a Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 22 Mar 2012 19:41:48 +0400 Subject: [PATCH 16/33] first draft of multicorsor (messy but mostly works) --- demo/kitchen-sink/demo.js | 3 + lib/ace/multi_cursor.js | 470 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 473 insertions(+) create mode 100644 lib/ace/multi_cursor.js diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 242b87a4..0a857a45 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -576,4 +576,7 @@ commands.addCommand({ } }); +// add multiple cursor support to editor +require("ace/multi_cursor").MultiCursor(env.editor) + }); diff --git a/lib/ace/multi_cursor.js b/lib/ace/multi_cursor.js new file mode 100644 index 00000000..d5e930ee --- /dev/null +++ b/lib/ace/multi_cursor.js @@ -0,0 +1,470 @@ +/* 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): + * Fabian Jakobs + * + * 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 Range = require("./range").Range; +var event = require("./lib/event"); + +function VirtualSelection(selection) { + this.session = selection.session; + this.doc = selection.doc; + + this.clearSelection(); + this.selectionLead = this.doc.createAnchor(0, 0); + this.selectionAnchor = this.doc.createAnchor(0, 0); + + this.selectionLead.detach() + this.selectionAnchor.detach() + + this.fromRange = function(r) { + this.setSelectionRange(r, r.cursor == r.start) + } + + this.toRange = function(r) { + var r1 = this.getRange() + + r.start.column = r1.start.column + r.start.row = r1.start.row + r.end.column = r1.end.column + r.end.row = r1.end.row + + r.cursor = this.isBackwards() ? r.start : r.end; + } +} +VirtualSelection.prototype = Selection.prototype; + + +function forEachSelection(editor, cmd, args) { + if (editor.session.multiSelection.inVirtualMode) + return + var session = editor.session + var selection = editor.selection + var rangeList = selection.rangeList + + var sh = new VirtualSelection(selection) + editor.session.multiSelection.inVirtualMode = true + for (var i = rangeList.ranges.length; i--;) { + sh.fromRange(rangeList.ranges[i]) + editor.selection = session.selection = sh + cmd.exec(editor, args || {}) + + sh.toRange(rangeList.ranges[i]) + } + + rangeList.merge() + editor.selection = session.selection = selection + editor.session.multiSelection.inVirtualMode = false + + selection.setSelectionRange(selection.rangeList.all[0]) + editor.renderer.updateCursor(); + editor.renderer.updateBackMarkers(); + + if (selection.rangeCount == 1 && editor.inMultiSelectMode) + exitMultiSelectMode(editor) +} + +function exec(command, editor, args) { + if (typeof command === 'string') + command = this.commands[command]; + + if (!command) + return false; + + if (editor && editor.$readOnly && !command.readOnly) + return false; + + if (/^(remove|insert|select|go|jumptomatching|del|backspace)/.test(command.name)) { + forEachSelection(editor, command, args) + }else + command.exec(editor, args || {}); + return true; +}; + +function enterMultiSelectMode(editor) { + if (editor.inMultiSelectMode) + return + editor.inMultiSelectMode = true + editor.setStyle("multiselect") + editor.keyBinding.addKeyboardHandler(exports.keyboardHandler); + editor.commands.__exec = editor.commands.exec + editor.commands.exec = exec + + editor.selection.rangeList.attach(editor.session); +} +function exitMultiSelectMode(editor) { + if (editor.session.multiSelection.inVirtualMode) + return + editor.inMultiSelectMode = false; + editor.selection.secondarySelections = []; + editor.unsetStyle("multiselect"); + editor.selection.rangeList.removeAll(); + editor.keyBinding.removeKeyboardHandler(exports.keyboardHandler); + + editor.commands.exec = editor.commands.__exec; + editor.renderer.updateCursor(); + editor.renderer.updateBackMarkers(); + + editor.selection.rangeList.dettach(editor.session); +} + +function initSession(session) { + if (session.selection.rangeList) + return + session.multiSelection = session.selection + + var rangeList = new RangeList; + + rangeList.all = []; + rangeList.on("add", function(e) { + rangeList.all.unshift(e.range) + }) + + rangeList.on("remove", function(e) { + var ranges = e.ranges + for (var i = ranges.length; i--; ) { + var index = rangeList.all.indexOf(ranges[i]); + rangeList.all.splice(index, 1); + } + }); + + session.selection.rangeList = rangeList; + session.selection.cursor = session.selection.selectionLead; + session.selection.secondarySelections = []; + session.selection.getAllRanges = function() { + return this.rangeList.ranges.concat(this.secondarySelections) + }; + session.selection.rangeCount = 1; +} + +var addSelectionRange = function(editor, orientedRange) { + if (!editor.inMultiSelectMode) + enterMultiSelectMode(editor) + + if (!orientedRange.cursor) + orientedRange.cursor = orientedRange.end + + var style = editor.getSelectionStyle(); + orientedRange.marker = editor.session.addMarker(orientedRange, "ace_selection", style); + + // use this to not conflict with virtualSelections added by forEachSelection + var selection = editor.session.multiSelection; + selection.rangeList.add(orientedRange); + selection.rangeCount = selection.rangeList.all.length + selection.secondarySelections.length; + + selection.setSelectionRange(orientedRange, orientedRange.cursor == orientedRange.start) + editor.renderer.updateCursor(); + editor.renderer.updateBackMarkers(); +}; + +function addCursorV(editor, dir){ + var range = editor.selection.getRange() + var isBackwards = editor.selection.isBackwards() + range.cursor = isBackwards ? range.start : range.end; + + var screenLead = editor.session.documentToScreenPosition(range.cursor); + var lead = editor.session.screenToDocumentPosition(screenLead.row + dir, screenLead.column); + + if (!range.isEmpty()) { + var screenAnchor = editor.session.documentToScreenPosition(isBackwards ? range.end : range.start); + var anchor = editor.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 + } + if (!editor.inMultiSelectMode) { + addSelectionRange(editor, range) + } else { + var allRanges = editor.selection.rangeList.ranges + // remove range if at end + if (range.isEequal(allRanges[dir == 1 ? 0 : allRanges.length - 1])) + var toRemove = range.cursor + } + addSelectionRange(editor, newRange) + if (toRemove) + editor.selection.rangeList.substractPoint(toRemove) +} +// commands +exports.defaultCommands = [{ + name: "addCursorAbove", + exec: function(editor) { + addCursorV(editor, -1) + }, + bindKey: { + win: "Alt-Shift-Up", + mac: "Alt-Shift-Up" + } +}, { + name: "addCursorBelow", + exec: function(editor) { + addCursorV(editor, 1) + }, + bindKey: { + win: "Alt-Shift-Down", + mac: "Alt-Shift-Down" + } +}]; + +exports.multiEditCommands = [{ + name: "singleSelection", + bindKey: "esc", + exec: function(editor) { + console.log(editor) + exitMultiSelectMode(editor) + }, +}]; + +var HashHandler = require("ace/keyboard/hash_handler").HashHandler; +exports.keyboardHandler = new HashHandler(exports.multiEditCommands); + +// 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 = ev.ctrlKey; + var button = e.getButton(); + + if (!ctrl && !alt) { + if (e.editor.selection.rangeCount > 1) { + if (button == 0) { + exitMultiSelectMode(e.editor) + } 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 = selection.rangeCount > 1 + var pos = e.getDocumentPosition(); + var rangeList = selection.rangeList; + var cursor = selection.getCursor() + var inSelection = e.inSelection() || (selection.isEmpty() && isSamePoint(pos, cursor)); + + if (ctrl && !shift && !alt && button == 0) { + if (!isMultiSelect && inSelection) + return // dragging + + if (!isMultiSelect) { + var tmpSel = selection.getRange(); + tmpSel.cursor = selection.isBackwards() ? tmpSel.start : tmpSel.end; + addSelectionRange(editor, tmpSel) + } + if (inSelection) + selection.clearSelection(); + + selection.secondarySelections.push(selection) + selection.rangeCount++ + var oldRange = rangeList.substractPoint(pos) + + event.capture(editor.container, function(){}, function() { + var i = selection.secondarySelections.indexOf(selection); + if (i != -1) { + selection.rangeCount-- + selection.secondarySelections.splice(i, 1); + } + + var tmpSel = selection.getRange(); + tmpSel.cursor = selection.isBackwards() ? tmpSel.start : tmpSel.end; + + if (oldRange && oldRange.isEmpty() && tmpSel.isEequal(oldRange)) { + var range = selection.rangeList.all[0] + editor.selection.rangeList._emit("remove", {ranges: []}) + selection.setSelectionRange(range, range.cursor == range.start) + selection._emit("changeSelection"); + selection._emit("changeCursor"); + return; + } + + addSelectionRange(editor, tmpSel) + }); + + //e.stop() + } else if (!shift && alt && button == 0) { + e.stop() + var mouseX = e.pageX, mouseY = e.pageY; + var onMouseSelection = function(e) { + mouseX = event.getDocumentX(e); + mouseY = event.getDocumentY(e); + }; + + selection.moveCursorToPosition(pos); + selection.clearSelection(); + if (!isMultiSelect) { + enterMultiSelectMode(editor) + selection.rangeCount = 5 + } + + + var rectSel = [] + selection.secondarySelections = rectSel + + var session = editor.session + var style = editor.getSelectionStyle(); + function addMarker(range) { + range.marker = session.addMarker(range, "ace_selection", style); + } + function removeMarker(range) { + session.removeMarker(range.marker); + } + + + + var onMouseSelectionEnd = function(e) { + clearInterval(timerId); + rectSel.forEach(removeMarker) + selection.secondarySelections = []; + for (var i = rectSel.length; i--; ) + addSelectionRange(editor, rectSel[i]) + }; + + var anchor = selection.getCursor(); + var screenAnchor = session.documentToScreenPosition(anchor); + var screenCursor = screenAnchor + var screenLength = session.getScreenLength() - 1 + + var onSelectionInterval = function() { + var newCursor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY); + if (isSamePoint(screenCursor, newCursor)) + return + rectSel.forEach(removeMarker) + rectSel.splice(0, rectSel.length) + screenCursor = newCursor + 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 (endRow > screenLength) + endRow = screenLength + + for (var row = startRow; row <= endRow; row++) { + var r = Range.fromPoints( + session.screenToDocumentPosition(row, startColumn), + session.screenToDocumentPosition(row, endColumn) + ) + r.cursor = xBackwards ? r.start : r.end + rectSel.push(r) + } + + rectSel.forEach(addMarker) + var lastIndex = yBackwards ? 0 : rectSel.length - 1 + selection.moveCursorToPosition(rectSel[lastIndex].cursor); + selection.clearSelection() + editor.renderer.scrollCursorIntoView(); + + editor.renderer.updateCursor(); + editor.renderer.updateBackMarkers(); + }; + + event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); + var timerId = setInterval(onSelectionInterval, 20); + + return e.preventDefault(); + } +} + +function altSelect(ev) { + +} + +// MultiCursor +function MultiCursor(editor) { + initSession(editor.session); + editor.on("changeSession", function(e) { + initSession(e.session) + }.bind(editor)); + + editor.selection.rangeList.on("remove", function(e) { + var ranges = e.ranges; + for (var i = ranges.length; i--; ) { + var range = ranges[i]; + if (range.marker != null) + this.session.removeMarker(range.marker); + + this.rangeCount --; + } + + if (this.rangeCount == 1 && editor.inMultiSelectMode) + exitMultiSelectMode(editor) + }.bind(editor.selection)); + + editor.on("mousedown", onMouseDown); + editor.commands.addCommands(exports.defaultCommands); +} + + + +exports.MultiCursor = MultiCursor; + +}); \ No newline at end of file From bcf8430ec4abf37ce5cb9d9894d29bcfe801de5a Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 22 Mar 2012 23:13:41 +0400 Subject: [PATCH 17/33] can edit text with multiple selections --- lib/ace/multi_cursor.js | 44 +++++++++++++++++++++++++++++++----- lib/ace/range_list.js | 45 ++++++++++++++++++++++++++++++++++--- lib/ace/virtual_renderer.js | 13 +++++++++++ 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/lib/ace/multi_cursor.js b/lib/ace/multi_cursor.js index d5e930ee..db286363 100644 --- a/lib/ace/multi_cursor.js +++ b/lib/ace/multi_cursor.js @@ -52,8 +52,25 @@ function VirtualSelection(selection) { this.selectionLead = this.doc.createAnchor(0, 0); this.selectionAnchor = this.doc.createAnchor(0, 0); - this.selectionLead.detach() - this.selectionAnchor.detach() + /*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(); + }); + + this.selectionAnchor.on("change", function() { + if (!_self.$isEmpty) + _self._emit("changeSelection"); + }); */ + + this.detach = function() { + this.selectionLead.detach(); + this.selectionAnchor.detach(); + this.session = this.doc = null; + } this.fromRange = function(r) { this.setSelectionRange(r, r.cursor == r.start) @@ -80,6 +97,9 @@ function forEachSelection(editor, cmd, args) { var selection = editor.selection var rangeList = selection.rangeList + var reg = selection._eventRegistry; + selection._eventRegistry = {}; + var sh = new VirtualSelection(selection) editor.session.multiSelection.inVirtualMode = true for (var i = rangeList.ranges.length; i--;) { @@ -89,10 +109,12 @@ function forEachSelection(editor, cmd, args) { sh.toRange(rangeList.ranges[i]) } + sh.detach(); rangeList.merge() - editor.selection = session.selection = selection - editor.session.multiSelection.inVirtualMode = false + editor.selection = session.selection = selection; + editor.session.multiSelection.inVirtualMode = false; + selection._eventRegistry = reg; selection.setSelectionRange(selection.rangeList.all[0]) editor.renderer.updateCursor(); @@ -128,6 +150,7 @@ function enterMultiSelectMode(editor) { editor.commands.__exec = editor.commands.exec editor.commands.exec = exec + editor.session.$undoSelect = false editor.selection.rangeList.attach(editor.session); } function exitMultiSelectMode(editor) { @@ -143,7 +166,9 @@ function exitMultiSelectMode(editor) { editor.renderer.updateCursor(); editor.renderer.updateBackMarkers(); - editor.selection.rangeList.dettach(editor.session); + editor.session.$undoSelect = true + + editor.selection.rangeList.detach(editor.session); } function initSession(session) { @@ -347,7 +372,7 @@ function onMouseDown(e) { selection.clearSelection(); if (!isMultiSelect) { enterMultiSelectMode(editor) - selection.rangeCount = 5 + selection.rangeCount = Infinity } @@ -371,6 +396,13 @@ function onMouseDown(e) { selection.secondarySelections = []; for (var i = rectSel.length; i--; ) addSelectionRange(editor, rectSel[i]) + + if (selection.rangeCount == Infinity) { + selection.rangeCount = selection.rangeList.all.length + selection.secondarySelections.length; + + if (selection.rangeCount <= 1) + exitMultiSelectMode(editor) + } }; var anchor = selection.getCursor(); diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js index fcf9b250..9777b5ab 100644 --- a/lib/ace/range_list.js +++ b/lib/ace/range_list.js @@ -170,7 +170,7 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { this.attach = function(session) { if (this.session) - this.dettach(); + this.detach(); this.session = session; this.onChange = this.$onChange.bind(this); @@ -178,7 +178,7 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { this.session.on('change', this.onChange); }; - this.dettach = function() { + this.detach = function() { if (!this.session) return; this.session.removeListener('change', this.onChange) @@ -186,7 +186,46 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { }; this.$onChange = function(e) { - // todo + 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); diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 0e6b9f7c..66717d9d 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -736,6 +736,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(); From ec9ef6c84397b792c114f530dfa6d79e5a06ecca Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 25 Mar 2012 12:15:33 +0500 Subject: [PATCH 18/33] fix handling of desiredcolumn --- lib/ace/multi_cursor.js | 704 +++++++++++++++++++--------------------- lib/ace/selection.js | 40 ++- 2 files changed, 363 insertions(+), 381 deletions(-) diff --git a/lib/ace/multi_cursor.js b/lib/ace/multi_cursor.js index db286363..9b45ee70 100644 --- a/lib/ace/multi_cursor.js +++ b/lib/ace/multi_cursor.js @@ -44,221 +44,178 @@ var Selection = require("./selection").Selection; var Range = require("./range").Range; var event = require("./lib/event"); -function VirtualSelection(selection) { - this.session = selection.session; - this.doc = selection.doc; - - this.clearSelection(); - this.selectionLead = this.doc.createAnchor(0, 0); - this.selectionAnchor = this.doc.createAnchor(0, 0); - - /*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(); - }); - - this.selectionAnchor.on("change", function() { - if (!_self.$isEmpty) - _self._emit("changeSelection"); - }); */ - - this.detach = function() { - this.selectionLead.detach(); - this.selectionAnchor.detach(); - this.session = this.doc = null; - } - - this.fromRange = function(r) { - this.setSelectionRange(r, r.cursor == r.start) - } - - this.toRange = function(r) { - var r1 = this.getRange() - - r.start.column = r1.start.column - r.start.row = r1.start.row - r.end.column = r1.end.column - r.end.row = r1.end.row - - r.cursor = this.isBackwards() ? r.start : r.end; - } -} -VirtualSelection.prototype = Selection.prototype; - function forEachSelection(editor, cmd, args) { - if (editor.session.multiSelection.inVirtualMode) - return - var session = editor.session - var selection = editor.selection - var rangeList = selection.rangeList - - var reg = selection._eventRegistry; - selection._eventRegistry = {}; - - var sh = new VirtualSelection(selection) - editor.session.multiSelection.inVirtualMode = true - for (var i = rangeList.ranges.length; i--;) { - sh.fromRange(rangeList.ranges[i]) - editor.selection = session.selection = sh - cmd.exec(editor, args || {}) - - sh.toRange(rangeList.ranges[i]) - } - sh.detach(); - - rangeList.merge() - editor.selection = session.selection = selection; - editor.session.multiSelection.inVirtualMode = false; - selection._eventRegistry = reg; - - selection.setSelectionRange(selection.rangeList.all[0]) - editor.renderer.updateCursor(); - editor.renderer.updateBackMarkers(); - - if (selection.rangeCount == 1 && editor.inMultiSelectMode) - exitMultiSelectMode(editor) + if (editor.session.multiSelection.inVirtualMode) + return + var session = editor.session + var selection = editor.selection + var rangeList = selection.rangeList + + var reg = selection._eventRegistry; + selection._eventRegistry = {}; + + var sh = new Selection(session); + editor.session.multiSelection.inVirtualMode = true; + for (var i = rangeList.ranges.length; i--;) { + sh.fromOrientedRange(rangeList.ranges[i]) + editor.selection = session.selection = sh + cmd.exec(editor, args || {}) + sh.toOrientedRange(rangeList.ranges[i]) + } + sh.detach(); + + rangeList.merge() + editor.selection = session.selection = selection; + editor.session.multiSelection.inVirtualMode = false; + selection._eventRegistry = reg; + + selection.fromOrientedRange(selection.rangeList.all[0]) + editor.renderer.updateCursor(); + editor.renderer.updateBackMarkers(); + + if (selection.rangeCount == 1 && editor.inMultiSelectMode) + exitMultiSelectMode(editor) } function exec(command, editor, args) { - if (typeof command === 'string') - command = this.commands[command]; + if (typeof command === 'string') + command = this.commands[command]; - if (!command) - return false; + if (!command) + return false; - if (editor && editor.$readOnly && !command.readOnly) - return false; + if (editor && editor.$readOnly && !command.readOnly) + return false; - if (/^(remove|insert|select|go|jumptomatching|del|backspace)/.test(command.name)) { - forEachSelection(editor, command, args) - }else - command.exec(editor, args || {}); - return true; + if (/^(remove|insert|select|go|jumptomatching|del|backspace|to(?:upp|low)ercase)/.test(command.name)) { + forEachSelection(editor, command, args) + }else + command.exec(editor, args || {}); + return true; }; function enterMultiSelectMode(editor) { - if (editor.inMultiSelectMode) - return - editor.inMultiSelectMode = true - editor.setStyle("multiselect") - editor.keyBinding.addKeyboardHandler(exports.keyboardHandler); - editor.commands.__exec = editor.commands.exec - editor.commands.exec = exec - - editor.session.$undoSelect = false - editor.selection.rangeList.attach(editor.session); + if (editor.inMultiSelectMode) + return + editor.inMultiSelectMode = true + editor.setStyle("multiselect") + editor.keyBinding.addKeyboardHandler(exports.keyboardHandler); + editor.commands.__exec = editor.commands.exec + editor.commands.exec = exec + + editor.session.$undoSelect = false + editor.selection.rangeList.attach(editor.session); } function exitMultiSelectMode(editor) { - if (editor.session.multiSelection.inVirtualMode) - return - editor.inMultiSelectMode = false; - editor.selection.secondarySelections = []; - editor.unsetStyle("multiselect"); - editor.selection.rangeList.removeAll(); - editor.keyBinding.removeKeyboardHandler(exports.keyboardHandler); - - editor.commands.exec = editor.commands.__exec; - editor.renderer.updateCursor(); - editor.renderer.updateBackMarkers(); - - editor.session.$undoSelect = true - - editor.selection.rangeList.detach(editor.session); + if (editor.session.multiSelection.inVirtualMode) + return + editor.inMultiSelectMode = false; + editor.selection.secondarySelections = []; + editor.unsetStyle("multiselect"); + editor.selection.rangeList.removeAll(); + editor.keyBinding.removeKeyboardHandler(exports.keyboardHandler); + + editor.commands.exec = editor.commands.__exec; + editor.renderer.updateCursor(); + editor.renderer.updateBackMarkers(); + + editor.session.$undoSelect = true + + editor.selection.rangeList.detach(editor.session); } function initSession(session) { - if (session.selection.rangeList) - return - session.multiSelection = session.selection - - var rangeList = new RangeList; - - rangeList.all = []; - rangeList.on("add", function(e) { - rangeList.all.unshift(e.range) - }) - - rangeList.on("remove", function(e) { - var ranges = e.ranges - for (var i = ranges.length; i--; ) { - var index = rangeList.all.indexOf(ranges[i]); - rangeList.all.splice(index, 1); - } - }); - - session.selection.rangeList = rangeList; - session.selection.cursor = session.selection.selectionLead; - session.selection.secondarySelections = []; - session.selection.getAllRanges = function() { - return this.rangeList.ranges.concat(this.secondarySelections) - }; - session.selection.rangeCount = 1; + if (session.selection.rangeList) + return + session.multiSelection = session.selection + + var rangeList = new RangeList; + + rangeList.all = []; + rangeList.on("add", function(e) { + rangeList.all.unshift(e.range) + }) + + rangeList.on("remove", function(e) { + var ranges = e.ranges + for (var i = ranges.length; i--; ) { + var index = rangeList.all.indexOf(ranges[i]); + rangeList.all.splice(index, 1); + } + }); + + session.selection.rangeList = rangeList; + session.selection.cursor = session.selection.selectionLead; + session.selection.secondarySelections = []; + session.selection.getAllRanges = function() { + return this.rangeList.ranges.concat(this.secondarySelections) + }; + session.selection.rangeCount = 1; } var addSelectionRange = function(editor, orientedRange) { - if (!editor.inMultiSelectMode) - enterMultiSelectMode(editor) - - if (!orientedRange.cursor) - orientedRange.cursor = orientedRange.end + if (!editor.inMultiSelectMode) + enterMultiSelectMode(editor) - var style = editor.getSelectionStyle(); - orientedRange.marker = editor.session.addMarker(orientedRange, "ace_selection", style); + if (!orientedRange.cursor) + orientedRange.cursor = orientedRange.end - // use this to not conflict with virtualSelections added by forEachSelection - var selection = editor.session.multiSelection; - selection.rangeList.add(orientedRange); - selection.rangeCount = selection.rangeList.all.length + selection.secondarySelections.length; - - selection.setSelectionRange(orientedRange, orientedRange.cursor == orientedRange.start) - editor.renderer.updateCursor(); - editor.renderer.updateBackMarkers(); + var style = editor.getSelectionStyle(); + orientedRange.marker = editor.session.addMarker(orientedRange, "ace_selection", style); + + // use this to not conflict with virtualSelections added by forEachSelection + var selection = editor.session.multiSelection; + selection.rangeList.add(orientedRange); + selection.rangeCount = selection.rangeList.all.length + selection.secondarySelections.length; + + selection.fromOrientedRange(orientedRange) + editor.renderer.updateCursor(); + editor.renderer.updateBackMarkers(); }; function addCursorV(editor, dir){ - var range = editor.selection.getRange() - var isBackwards = editor.selection.isBackwards() - range.cursor = isBackwards ? range.start : range.end; - - var screenLead = editor.session.documentToScreenPosition(range.cursor); - var lead = editor.session.screenToDocumentPosition(screenLead.row + dir, screenLead.column); - - if (!range.isEmpty()) { - var screenAnchor = editor.session.documentToScreenPosition(isBackwards ? range.end : range.start); - var anchor = editor.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 - } - if (!editor.inMultiSelectMode) { - addSelectionRange(editor, range) - } else { - var allRanges = editor.selection.rangeList.ranges - // remove range if at end - if (range.isEequal(allRanges[dir == 1 ? 0 : allRanges.length - 1])) - var toRemove = range.cursor - } - addSelectionRange(editor, newRange) - if (toRemove) - editor.selection.rangeList.substractPoint(toRemove) + var range = editor.selection.getRange() + var isBackwards = editor.selection.isBackwards() + range.cursor = isBackwards ? range.start : range.end; + + var screenLead = editor.session.documentToScreenPosition(range.cursor); + screenLead.column = editor.selection.$desiredColumn; + + var lead = editor.session.screenToDocumentPosition(screenLead.row + dir, screenLead.column); + + if (!range.isEmpty()) { + var screenAnchor = editor.session.documentToScreenPosition(isBackwards ? range.end : range.start); + var anchor = editor.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 = editor.selection.$desiredColumn + if (!editor.inMultiSelectMode) { + addSelectionRange(editor, range) + } else { + var allRanges = editor.selection.rangeList.ranges + // remove range if at end + if (range.isEequal(allRanges[dir == 1 ? 0 : allRanges.length - 1])) + var toRemove = range.cursor + } + addSelectionRange(editor, newRange) + if (toRemove) + editor.selection.rangeList.substractPoint(toRemove) } // commands exports.defaultCommands = [{ name: "addCursorAbove", exec: function(editor) { - addCursorV(editor, -1) + addCursorV(editor, -1) }, bindKey: { win: "Alt-Shift-Up", @@ -267,7 +224,7 @@ exports.defaultCommands = [{ }, { name: "addCursorBelow", exec: function(editor) { - addCursorV(editor, 1) + addCursorV(editor, 1) }, bindKey: { win: "Alt-Shift-Down", @@ -279,7 +236,7 @@ exports.multiEditCommands = [{ name: "singleSelection", bindKey: "esc", exec: function(editor) { - console.log(editor) + console.log(editor) exitMultiSelectMode(editor) }, }]; @@ -289,208 +246,205 @@ exports.keyboardHandler = new HashHandler(exports.multiEditCommands); // mouse function isSamePoint(p1, p2) { - return p1.row == p2.row && p1.column == p2.column + 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 = ev.ctrlKey; - var button = e.getButton(); - - if (!ctrl && !alt) { - if (e.editor.selection.rangeCount > 1) { - if (button == 0) { - exitMultiSelectMode(e.editor) - } 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 = selection.rangeCount > 1 - var pos = e.getDocumentPosition(); - var rangeList = selection.rangeList; - var cursor = selection.getCursor() - var inSelection = e.inSelection() || (selection.isEmpty() && isSamePoint(pos, cursor)); - - if (ctrl && !shift && !alt && button == 0) { - if (!isMultiSelect && inSelection) - return // dragging - - if (!isMultiSelect) { - var tmpSel = selection.getRange(); - tmpSel.cursor = selection.isBackwards() ? tmpSel.start : tmpSel.end; - addSelectionRange(editor, tmpSel) - } - if (inSelection) - selection.clearSelection(); - - selection.secondarySelections.push(selection) - selection.rangeCount++ - var oldRange = rangeList.substractPoint(pos) - - event.capture(editor.container, function(){}, function() { - var i = selection.secondarySelections.indexOf(selection); - if (i != -1) { - selection.rangeCount-- - selection.secondarySelections.splice(i, 1); - } - - var tmpSel = selection.getRange(); - tmpSel.cursor = selection.isBackwards() ? tmpSel.start : tmpSel.end; + var ev = e.domEvent; + var alt = ev.altKey; + var shift = ev.shiftKey; + var ctrl = ev.ctrlKey; + var button = e.getButton(); - if (oldRange && oldRange.isEmpty() && tmpSel.isEequal(oldRange)) { - var range = selection.rangeList.all[0] - editor.selection.rangeList._emit("remove", {ranges: []}) - selection.setSelectionRange(range, range.cursor == range.start) - selection._emit("changeSelection"); - selection._emit("changeCursor"); - return; - } + if (!ctrl && !alt) { + if (e.editor.selection.rangeCount > 1) { + if (button == 0) { + exitMultiSelectMode(e.editor) + } 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; + } - addSelectionRange(editor, tmpSel) - }); + var editor = e.editor; + var selection = editor.selection; + var isMultiSelect = selection.rangeCount > 1 + var pos = e.getDocumentPosition(); + var rangeList = selection.rangeList; + var cursor = selection.getCursor() + var inSelection = e.inSelection() || (selection.isEmpty() && isSamePoint(pos, cursor)); - //e.stop() - } else if (!shift && alt && button == 0) { - e.stop() - var mouseX = e.pageX, mouseY = e.pageY; - var onMouseSelection = function(e) { - mouseX = event.getDocumentX(e); - mouseY = event.getDocumentY(e); - }; + if (ctrl && !shift && !alt && button == 0) { + if (!isMultiSelect && inSelection) + return // dragging - selection.moveCursorToPosition(pos); - selection.clearSelection(); - if (!isMultiSelect) { - enterMultiSelectMode(editor) - selection.rangeCount = Infinity - } - - - var rectSel = [] - selection.secondarySelections = rectSel - - var session = editor.session - var style = editor.getSelectionStyle(); - function addMarker(range) { - range.marker = session.addMarker(range, "ace_selection", style); - } - function removeMarker(range) { - session.removeMarker(range.marker); - } - - - - var onMouseSelectionEnd = function(e) { - clearInterval(timerId); - rectSel.forEach(removeMarker) - selection.secondarySelections = []; - for (var i = rectSel.length; i--; ) - addSelectionRange(editor, rectSel[i]) - - if (selection.rangeCount == Infinity) { - selection.rangeCount = selection.rangeList.all.length + selection.secondarySelections.length; - - if (selection.rangeCount <= 1) - exitMultiSelectMode(editor) - } - }; - - var anchor = selection.getCursor(); - var screenAnchor = session.documentToScreenPosition(anchor); - var screenCursor = screenAnchor - var screenLength = session.getScreenLength() - 1 - - var onSelectionInterval = function() { - var newCursor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY); - if (isSamePoint(screenCursor, newCursor)) - return - rectSel.forEach(removeMarker) - rectSel.splice(0, rectSel.length) - screenCursor = newCursor - 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 (endRow > screenLength) - endRow = screenLength - - for (var row = startRow; row <= endRow; row++) { - var r = Range.fromPoints( - session.screenToDocumentPosition(row, startColumn), - session.screenToDocumentPosition(row, endColumn) - ) - r.cursor = xBackwards ? r.start : r.end - rectSel.push(r) - } - - rectSel.forEach(addMarker) - var lastIndex = yBackwards ? 0 : rectSel.length - 1 - selection.moveCursorToPosition(rectSel[lastIndex].cursor); - selection.clearSelection() - editor.renderer.scrollCursorIntoView(); - - editor.renderer.updateCursor(); - editor.renderer.updateBackMarkers(); - }; - - event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); - var timerId = setInterval(onSelectionInterval, 20); + if (!isMultiSelect) { + addSelectionRange(editor, selection.toOrientedRange()) + } + if (inSelection) + selection.clearSelection(); - return e.preventDefault(); - } + selection.secondarySelections.push(selection) + selection.rangeCount++ + var oldRange = rangeList.substractPoint(pos) + + event.capture(editor.container, function(){}, function() { + var i = selection.secondarySelections.indexOf(selection); + if (i != -1) { + selection.rangeCount-- + selection.secondarySelections.splice(i, 1); + } + + var tmpSel = selection.toOrientedRange(); + + if (oldRange && oldRange.isEmpty() && tmpSel.isEequal(oldRange)) { + var range = selection.rangeList.all[0] + editor.selection.rangeList._emit("remove", {ranges: []}) + selection.setSelectionRange(range, range.cursor == range.start) + selection._emit("changeSelection"); + selection._emit("changeCursor"); + return; + } + + addSelectionRange(editor, tmpSel) + }); + + //e.stop() + } else if (!shift && alt && button == 0) { + e.stop() + var mouseX = e.pageX, mouseY = e.pageY; + var onMouseSelection = function(e) { + mouseX = event.getDocumentX(e); + mouseY = event.getDocumentY(e); + }; + + selection.moveCursorToPosition(pos); + selection.clearSelection(); + if (!isMultiSelect) { + enterMultiSelectMode(editor) + selection.rangeCount = Infinity + } + + + var rectSel = [] + selection.secondarySelections = rectSel + + var session = editor.session + var style = editor.getSelectionStyle(); + function addMarker(range) { + range.marker = session.addMarker(range, "ace_selection", style); + } + function removeMarker(range) { + session.removeMarker(range.marker); + } + + + + var onMouseSelectionEnd = function(e) { + clearInterval(timerId); + rectSel.forEach(removeMarker) + selection.secondarySelections = []; + for (var i = rectSel.length; i--; ) + addSelectionRange(editor, rectSel[i]) + + if (selection.rangeCount == Infinity) { + selection.rangeCount = selection.rangeList.all.length + selection.secondarySelections.length; + + if (selection.rangeCount <= 1) + exitMultiSelectMode(editor) + } + }; + + var anchor = selection.getCursor(); + var screenAnchor = session.documentToScreenPosition(anchor); + var screenCursor = screenAnchor + var screenLength = session.getScreenLength() - 1 + + var onSelectionInterval = function() { + var newCursor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY); + if (isSamePoint(screenCursor, newCursor)) + return + rectSel.forEach(removeMarker) + rectSel.splice(0, rectSel.length) + screenCursor = newCursor + 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 (endRow > screenLength) + endRow = screenLength + + for (var row = startRow; row <= endRow; row++) { + var r = Range.fromPoints( + session.screenToDocumentPosition(row, startColumn), + session.screenToDocumentPosition(row, endColumn) + ) + r.cursor = xBackwards ? r.start : r.end + rectSel.push(r) + } + + rectSel.forEach(addMarker) + var lastIndex = yBackwards ? 0 : rectSel.length - 1 + selection.moveCursorToPosition(rectSel[lastIndex].cursor); + selection.clearSelection() + editor.renderer.scrollCursorIntoView(); + + editor.renderer.updateCursor(); + editor.renderer.updateBackMarkers(); + }; + + event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); + var timerId = setInterval(onSelectionInterval, 20); + + return e.preventDefault(); + } } - + function altSelect(ev) { - -} + +} // MultiCursor function MultiCursor(editor) { - initSession(editor.session); - editor.on("changeSession", function(e) { - initSession(e.session) - }.bind(editor)); + initSession(editor.session); + editor.on("changeSession", function(e) { + initSession(e.session) + }.bind(editor)); - editor.selection.rangeList.on("remove", function(e) { - var ranges = e.ranges; - for (var i = ranges.length; i--; ) { - var range = ranges[i]; - if (range.marker != null) - this.session.removeMarker(range.marker); + editor.selection.rangeList.on("remove", function(e) { + var ranges = e.ranges; + for (var i = ranges.length; i--; ) { + var range = ranges[i]; + if (range.marker != null) + this.session.removeMarker(range.marker); - this.rangeCount --; - } + this.rangeCount --; + } + + if (this.rangeCount == 1 && editor.inMultiSelectMode) + exitMultiSelectMode(editor) + }.bind(editor.selection)); - if (this.rangeCount == 1 && editor.inMultiSelectMode) - exitMultiSelectMode(editor) - }.bind(editor.selection)); - editor.on("mousedown", onMouseDown); editor.commands.addCommands(exports.defaultCommands); } diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 810a0289..7827d359 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -392,14 +392,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 +408,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 +433,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); @@ -504,6 +504,34 @@ var Selection = function(session) { this.moveCursorTo(row, column, preventUpdateDesiredColumn); }; + // 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; From fc500bb37aeb5fc0cf5807e798a38365279f7420 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 25 Mar 2012 14:52:17 +0400 Subject: [PATCH 19/33] reset desiredColumn when needed instead of updating it all the time --- lib/ace/multi_cursor.js | 3 ++- lib/ace/selection.js | 49 ++++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/ace/multi_cursor.js b/lib/ace/multi_cursor.js index 9b45ee70..3e948318 100644 --- a/lib/ace/multi_cursor.js +++ b/lib/ace/multi_cursor.js @@ -180,7 +180,8 @@ function addCursorV(editor, dir){ range.cursor = isBackwards ? range.start : range.end; var screenLead = editor.session.documentToScreenPosition(range.cursor); - screenLead.column = editor.selection.$desiredColumn; + if (editor.selection.$desiredColumn) + screenLead.column = editor.selection.$desiredColumn; var lead = editor.session.screenToDocumentPosition(screenLead.row + dir, screenLead.column); diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 7827d359..d2ae3c6a 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -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) { @@ -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,19 +490,17 @@ 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 From d85e8b24f65386e1e1f7f39829331c8c35849fc1 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 25 Mar 2012 22:16:28 +0400 Subject: [PATCH 20/33] add some more commands --- lib/ace/multi_cursor.js | 161 ++++++++++++++++++++++++++++++++++------ 1 file changed, 138 insertions(+), 23 deletions(-) diff --git a/lib/ace/multi_cursor.js b/lib/ace/multi_cursor.js index 3e948318..14b73a1d 100644 --- a/lib/ace/multi_cursor.js +++ b/lib/ace/multi_cursor.js @@ -88,10 +88,16 @@ function exec(command, editor, args) { if (editor && editor.$readOnly && !command.readOnly) return false; - if (/^(remove|insert|select|go|jumptomatching|del|backspace|to(?:upp|low)ercase)/.test(command.name)) { - forEachSelection(editor, command, args) - }else + if (!command.multiCursor) { command.exec(editor, args || {}); + } else if (command.multiCursor == "forEach") { + forEachSelection(editor, command, args) + } else if (command.multiCursor == "single") { + exitMultiSelectMode(editor); + command.exec(editor, args || {}); + } else { + command.multiCursor(editor, args || {}); + } return true; }; @@ -199,7 +205,7 @@ function addCursorV(editor, dir){ var newRange = Range.fromPoints(anchor, lead) newRange.cursor = newRange.end } - newRange.desiredColumn = editor.selection.$desiredColumn + newRange.desiredColumn = screenLead.column; if (!editor.inMultiSelectMode) { addSelectionRange(editor, range) } else { @@ -212,27 +218,140 @@ function addCursorV(editor, dir){ if (toRemove) editor.selection.rangeList.substractPoint(toRemove) } + +function transposeSelections(editor, dir) { + var session = editor.session; + var sel = session.multiSelection; + var all = sel.rangeList.all; + + 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(editor.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; + } +} + +function splitIntoLines(editor) { + var sel = editor.session.multiSelection + if (sel.rangeCount > 1) { + var ranges = sel.rangeList.ranges; + var lastRange = ranges[ranges.length - 1] + var range = Range.fromPoints(ranges[0].start, lastRange.end) + + exitMultiSelectMode(editor) + sel.setSelectionRange(range, lastRange.cursor == lastRange.start) + } else { + + } +} + +var Search = require("ace/search").Search +var search = new Search + +function find(session, needle, dir) { + search.$options.wrap = false; + search.$options.needle = needle; + search.$options.backwards = dir == -1; + return search.find(session) +} +function addRange(editor, dir, skip) { + var session = editor.session; + var sel = session.multiSelection; + var all = sel.rangeList.all; + + var range = sel.getRange(); + if (range.isEmpty()) { + var tmp = session.getWordRange(range.start.row, range.start.column) + var offset = tmp.start.column - range.start.column + range = tmp; + } + var needle = session.getTextRange(range); + + if (skip) { + + } + + var newRange = find(session, needle, dir); + + newRange.cursor = dir == -1 ? newRange.start : newRange.end; + addSelectionRange(editor, newRange) +} // commands +// add multicursor annotations to default commands +var defaultCommands = require("./commands/default_commands").commands; + +defaultCommands.forEach(function(command) { + var single = RegExp(["selectall"].join("|"), ""); + var mapOverCommands = RegExp(["backspace", "del", + "golinedown", "golineup", "gotoend", "gotoleft", "gotolineend", "gotolinestart", + "gotoright", "gotostart", "gotowordleft", "gotowordright", + "indent", "insertstring", "inserttext", "jumptomatching", "outdent", + "removetolineend", "removetolinestart", "removewordleft", "removewordright", + "selectdown", "selectleft", "selectlineend", "selectlinestart", "selectright", + "selecttoend", "selecttolineend", "selecttolinestart", "selecttostart", + "selectup", "selectwordleft", "selectwordright", + "splitline", "tolowercase", "touppercase"].join("|"), ""); + + if (single.test(command.name)) + command.multiCursor = "single"; + else if (mapOverCommands.test(command.name)) + command.multiCursor = "forEach"; + else if (command.name == "transposeletters") + command.multiCursor = transposeSelections +}); + +// commands to to enter multicursor mode exports.defaultCommands = [{ name: "addCursorAbove", - exec: function(editor) { - addCursorV(editor, -1) - }, - bindKey: { - win: "Alt-Shift-Up", - mac: "Alt-Shift-Up" - } + exec: function(editor) {addCursorV(editor, -1); }, + bindKey: {win: "Alt-Shift-Up", mac: "Alt-Shift-Up"} }, { name: "addCursorBelow", - exec: function(editor) { - addCursorV(editor, 1) - }, - bindKey: { - win: "Alt-Shift-Down", - mac: "Alt-Shift-Down" - } -}]; + exec: function(editor) {addCursorV(editor, 1); }, + bindKey: {win: "Alt-Shift-Down", mac: "Alt-Shift-Down"} +}, { + name: "selectMoreBefore", + exec: function(editor) {addRange(editor, -1); }, + bindKey: {win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up"} +}, { + name: "selectMoreAfter", + exec: function(editor) {addRange(editor, 1); }, + bindKey: {win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down"} +}, { + name: "selectNextBefore", + exec: function(editor) {addCursorV(editor, -1, true); }, + bindKey: {win: "Ctrl-Shift-PageUp", mac: "Ctrl-Shift-PageUp"} +}, { + name: "selectNextAfter", + exec: function(editor) {addCursorV(editor, 1, true); }, + bindKey: {win: "Ctrl-Shift-PageDown", mac: "Ctrl-Shift-PageDown"} +}, { + name: "splitIntoLines", + exec: function(editor) {splitIntoLines(editor); }, + bindKey: {win: "Ctrl-Shift-L", mac: "Ctrl-Shift-L"} +}, ]; +// commands active when multiple cursors are present exports.multiEditCommands = [{ name: "singleSelection", bindKey: "esc", @@ -421,10 +540,6 @@ function onMouseDown(e) { } } -function altSelect(ev) { - -} - // MultiCursor function MultiCursor(editor) { initSession(editor.session); From fdf9841ef9186b9ad898f0554211d827eba0ea7c Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 26 Mar 2012 11:49:54 +0400 Subject: [PATCH 21/33] keep option in demo across reloads --- demo/kitchen-sink/demo.js | 73 +++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 0a857a45..8b1420a4 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -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; @@ -96,6 +100,7 @@ function loadTheme(name, callback) { net.loadScript(fileName, callback); } +//{ var modes = [ new Mode("c_cpp", "C/C++", require("ace/mode/c_cpp").Mode, ["c", "cpp", "cxx", "h", "hpp"]), new Mode("clojure", "Clojure", require("ace/mode/clojure").Mode, ["clj"]), @@ -254,6 +259,9 @@ var docs = [ ) ]; +//} + + var docsByName = {}; docs.forEach(function(d) { docsByName[d.name] = d; @@ -327,36 +335,47 @@ bindDropdown("doc", function(value) { env.editor.focus(); }); +bindDropdown("mode", function(value) { + env.editor.getSession().setMode(modesByName[value].mode || modesByName.text.mode); +}); + + function updateUIEditorOptions() { var editor = env.editor; var session = editor.session; - docEl.value = session.name; - modeEl.value = session.getMode().name || "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.getMode().name || "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(); - 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(softTabEl, session.getUseSoftTabs()); + saveOption(behavioursEl, editor.getBehavioursEnabled()); +} + +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("mode", function(value) { - env.editor.getSession().setMode(modesByName[value].mode || modesByName.text.mode); -}); bindDropdown("theme", function(value) { if (!value) @@ -447,6 +466,7 @@ bindCheckbox("enable_behaviours", function(checked) { env.editor.setBehavioursEnabled(checked); }); + var secondSession = null; bindDropdown("split", function(value) { var sp = env.split; @@ -474,8 +494,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(); @@ -483,9 +507,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(); } @@ -576,7 +605,7 @@ commands.addCommand({ } }); -// add multiple cursor support to editor +// add multiple cursor support to editor require("ace/multi_cursor").MultiCursor(env.editor) }); From 2227a9bfeaccece2f346e076ccb00afca55c672b Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 26 Mar 2012 11:51:24 +0400 Subject: [PATCH 22/33] rename --- demo/kitchen-sink/demo.js | 2 +- lib/ace/{multi_cursor.js => multi_select.js} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename lib/ace/{multi_cursor.js => multi_select.js} (99%) diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 8b1420a4..18798f01 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -606,6 +606,6 @@ commands.addCommand({ }); // add multiple cursor support to editor -require("ace/multi_cursor").MultiCursor(env.editor) +require("ace/multi_select").MultiSelect(env.editor); }); diff --git a/lib/ace/multi_cursor.js b/lib/ace/multi_select.js similarity index 99% rename from lib/ace/multi_cursor.js rename to lib/ace/multi_select.js index 14b73a1d..5a425169 100644 --- a/lib/ace/multi_cursor.js +++ b/lib/ace/multi_select.js @@ -20,7 +20,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Fabian Jakobs + * Harutyun Amirjanyan * * 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 @@ -541,7 +541,7 @@ function onMouseDown(e) { } // MultiCursor -function MultiCursor(editor) { +function MultiSelect(editor) { initSession(editor.session); editor.on("changeSession", function(e) { initSession(e.session) @@ -567,6 +567,6 @@ function MultiCursor(editor) { -exports.MultiCursor = MultiCursor; +exports.MultiSelect = MultiSelect; }); \ No newline at end of file From 98180d5b079d54c3eada12d3328b81ba48bee417 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 26 Mar 2012 22:37:23 +0400 Subject: [PATCH 23/33] refactor --- lib/ace/commands/multi_select_commands.js | 111 +++ lib/ace/layer/cursor.js | 14 +- lib/ace/mouse/multi_select_mouse_handler.js | 173 ++++ lib/ace/multi_select.js | 956 ++++++++++---------- lib/ace/range_list.js | 8 +- 5 files changed, 770 insertions(+), 492 deletions(-) create mode 100644 lib/ace/commands/multi_select_commands.js create mode 100644 lib/ace/mouse/multi_select_mouse_handler.js diff --git a/lib/ace/commands/multi_select_commands.js b/lib/ace/commands/multi_select_commands.js new file mode 100644 index 00000000..3ec6778b --- /dev/null +++ b/lib/ace/commands/multi_select_commands.js @@ -0,0 +1,111 @@ +/* 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): + * Fabian Jakobs + * + * 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", + "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: "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); + +}); \ No newline at end of file diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index bf6c2901..36a085f3 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -66,7 +66,7 @@ var Cursor = function(parentEl) { this.addCursor = function() { var el = dom.createElement("div"); - var className = "ace_cursor" + var className = "ace_cursor"; if (!this.isVisible) className += " ace_hidden"; if (this.overwrite) @@ -81,8 +81,8 @@ var Cursor = function(parentEl) { this.removeCursor = function() { if (this.cursors.length > 1) { var el = this.cursors.pop(); - el.parentNode.removeChild(el) - return el + el.parentNode.removeChild(el); + return el; } }; @@ -141,8 +141,8 @@ var Cursor = function(parentEl) { this.update = function(config) { this.config = config; - if (this.session.selection.rangeCount > 1) { - var selections = this.session.selection.getAllRanges(); + if (this.session.selectionMarkerCount > 1) { + var selections = this.session.$selectionMarkers; var i = 0, sel, cursorIndex = 0; for (var i = selections.length; i--; ) { @@ -156,8 +156,8 @@ var Cursor = function(parentEl) { style.width = config.characterWidth + "px"; style.height = config.lineHeight + "px"; } - if (this.cursors.length > 1) - while (cursorIndex < this.cursors.length) + if (cursorIndex > 1) + while (this.cursors.length > cursorIndex) this.removeCursor(); } else { var pixelPos = this.getPixelPosition(null, true); diff --git a/lib/ace/mouse/multi_select_mouse_handler.js b/lib/ace/mouse/multi_select_mouse_handler.js new file mode 100644 index 00000000..5d8dcfd5 --- /dev/null +++ b/lib/ace/mouse/multi_select_mouse_handler.js @@ -0,0 +1,173 @@ +/* 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 + * + * 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 = ev.ctrlKey; + var button = e.getButton(); + + if (!ctrl && !alt) { + if (e.editor.selection.rangeCount > 1) { + 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 = selection.rangeCount > 1 + var pos = e.getDocumentPosition(); + var rangeList = selection.rangeList; + var cursor = selection.getCursor() + var inSelection = e.inSelection() || (selection.isEmpty() && isSamePoint(pos, cursor)); + + if (ctrl && !shift && !alt && button == 0) { + if (!isMultiSelect && inSelection) + return // dragging + + if (!isMultiSelect) { + selection.addRange(selection.toOrientedRange()); + } + if (inSelection) + selection.clearSelection(); + + var helper = selection.toOrientedRange(); + editor.addSelectionMarker(helper); + + var oldRange = rangeList.rangeAtPoint(pos); + + event.capture(editor.container, function(){}, function() { + editor.removeSelectionMarkers([helper]); + var tmpSel = selection.toOrientedRange(); + + if (oldRange && tmpSel.isEmpty() && isSamePoint(oldRange.cursor, tmpSel.cursor)) { + if (selection.rangeCount > 1) { + range = editor.selection.rangeList.substractPoint(tmpSel.cursor); + var range = editor.selection.rangeList.all[0]; + if (range) + editor.selection.addRange(range); + return; + } + } + + selection.addRange(tmpSel); + }); + + //e.stop() + } else if (!shift && alt && button == 0) { + e.stop() + var mouseX = e.pageX, mouseY = e.pageY; + var onMouseSelection = function(e) { + mouseX = event.getDocumentX(e); + mouseY = event.getDocumentY(e); + }; + + if (isMultiSelect && !ctrl) { + selection.single(); + } + selection.moveCursorToPosition(pos); + selection.clearSelection(); + + var rectSel = []; + var session = editor.session; + + var onMouseSelectionEnd = function(e) { + clearInterval(timerId); + editor.removeSelectionMarkers(rectSel); + for (var i = 0; i < rectSel.length; i++) + selection.addRange(rectSel[i]) + }; + + var anchor = selection.getCursor(); + var screenAnchor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY); + var clippedAnchor = session.documentToScreenPosition(anchor); + var screenCursor = screenAnchor; + + var onSelectionInterval = 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.renderer.updateCursor(); + editor.renderer.updateBackMarkers(); + }; + + event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); + var timerId = setInterval(onSelectionInterval, 20); + + return e.preventDefault(); + } +} + + + +exports.onMouseDown = onMouseDown; + +}); \ No newline at end of file diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 5a425169..3b5a8bd0 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -42,43 +42,445 @@ var RangeList = require("./range_list").RangeList; var Range = require("./range").Range; var Selection = require("./selection").Selection; var Range = require("./range").Range; -var event = require("./lib/event"); +var onMouseDown = require("./mouse/multi_select_mouse_handler").onMouseDown; +exports.commands = require("./commands/multi_select_commands"); -function forEachSelection(editor, cmd, args) { - if (editor.session.multiSelection.inVirtualMode) - return - var session = editor.session - var selection = editor.selection - var rangeList = selection.rangeList +var Search = require("ace/search").Search +var search = new Search - var reg = selection._eventRegistry; - selection._eventRegistry = {}; - - var sh = new Selection(session); - editor.session.multiSelection.inVirtualMode = true; - for (var i = rangeList.ranges.length; i--;) { - sh.fromOrientedRange(rangeList.ranges[i]) - editor.selection = session.selection = sh - cmd.exec(editor, args || {}) - sh.toOrientedRange(rangeList.ranges[i]) - } - sh.detach(); - - rangeList.merge() - editor.selection = session.selection = selection; - editor.session.multiSelection.inVirtualMode = false; - selection._eventRegistry = reg; - - selection.fromOrientedRange(selection.rangeList.all[0]) - editor.renderer.updateCursor(); - editor.renderer.updateBackMarkers(); - - if (selection.rangeCount == 1 && editor.inMultiSelectMode) - exitMultiSelectMode(editor) +function find(session, needle, dir) { + search.$options.wrap = true; + search.$options.needle = needle; + search.$options.backwards = dir == -1; + return search.find(session) } -function exec(command, editor, args) { +// extend EditSession +var EditSession = require("./edit_session").EditSession; +;(function() { + this.getSelectionMarkers = function() { + return this.$selectionMarkers; + }; +}).call(EditSession.prototype); + +// extend Selection +;(function() { + this.addRange = function(range) { + if (!range.cursor) + range.cursor = range.end; + + if (this.rangeCount == 0) { + var oldRange = this.toOrientedRange(); + this.rangeList.add(oldRange); + this._emit("addRange", {range: oldRange}); + } + + this.rangeList.add(range); + this.rangeCount = this.rangeList.ranges.length; + + if (this.rangeCount > 1 && !this.inMultiSelectMode) { + this._emit("multiSelect"); + this.inMultiSelectMode = true; + } + + this.fromOrientedRange(range); + if (this.rangeCount >= 1) + this._emit("addRange", {range: range}); + }; + + this.single = function(range) { + range = range || this.rangeList.all[0]; + this.rangeList.removeAll(); + range && this.fromOrientedRange(range); + }; + + this.$onRemoveRange = function(e) { + this.rangeCount = this.rangeList.ranges.length; + this._emit("removeRange", e); + + if (this.rangeCount <= 1 && this.inMultiSelectMode) { + this.inMultiSelectMode = false; + this._emit("singleSelect"); + + if (this.rangeCount == 1) + this.single(); + } + }; + + // adds multicursor support to selection + this.$initRangeList = function() { + if (this.rangeList) + return; + + var rangeList = new RangeList; + // list of ranges in reverse addition order + // rangeList.all[0] is the same as selection.getRange + rangeList.all = []; + rangeList.on("add", function(e) { + rangeList.all.unshift(e.range) + }) + rangeList.on("remove", function(e) { + var ranges = e.ranges + for (var i = ranges.length; i--; ) { + var index = rangeList.all.indexOf(ranges[i]); + rangeList.all.splice(index, 1); + } + }); + + this.rangeList = rangeList; + this.cursor = this.selectionLead; + + this.rangeCount = 1; + this.rangeList.on("remove", this.$onRemoveRange.bind(this)); + }; + this.getAllRanges = function() { + return this.rangeList.ranges.concat(this.secondarySelections) + }; + + + 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.single() + this.setSelectionRange(range, lastRange.cursor == lastRange.start) + } else { + var cursor = this.getRange().toScreenRange(); + range + } + }; + + 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.single() + 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); + } + }; + + 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.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); + // FixMe + this.commands.__SingleSelectionExec = this.commands.exec; + this.commands.exec = exports.exec; + this.renderer.updateCursor(); + this.renderer.updateBackMarkers(); + + this.session.$undoSelect = false; + this.selection.rangeList.attach(this.session); + }; + + this.$onSingleSelect = function(e) { + if (this.session.multiSelect.inVirtualMode) + return; + this.inMultiSelectMode = false; + + this.unsetStyle("multiselect"); + this.keyBinding.removeKeyboardHandler(exports.commands.keyboardHandler); + + this.commands.exec = this.commands.__SingleSelectionExec; + this.renderer.updateCursor(); + this.renderer.updateBackMarkers(); + + this.session.$undoSelect = true; + + this.selection.rangeList.detach(this.session); + }; + + 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; + rangeList.merge(); + + var lastRange = selection.rangeList.all[0] + lastRange && selection.fromOrientedRange(lastRange); + + selection._emit("changeSelection") + selection._emit("changeCursor") + this.renderer.updateCursor(); + this.renderer.updateBackMarkers(); + }; + + this.exitMultiSelectMode = function() { + if (this.inVirtualSelectionMode) + return; + this.multiSelect.single(); + }; + + // todo route copy/cut/paste through commandmanager + this.getCopyText = function() { + var text = ""; + if (this.inMultiSelectMode) { + var ranges = this.multiSelect.rangeList.ranges; + for (var i = 0; i < ranges.length; i++) { + text += this.session.getTextRange(ranges[i]); + } + } 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 + if (skip || range.isEequal(allRanges[dir == 1 ? 0 : allRanges.length - 1])) + var toRemove = range.cursor; + } + + this.selection.addRange(newRange); + if (toRemove) + this.selection.rangeList.substractPoint(toRemove); + } + + this.transposeSelections = function(dir) { + var session = this.session; + var sel = session.multiSelect; + var all = sel.rangeList.all; + + 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 all = sel.rangeList.all; + + 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.rangeList.substractPoint(range.cursor); + } + + +}).call(Editor.prototype); + +// Todo emit event before exec? +exports.exec = function(command, editor, args) { if (typeof command === 'string') command = this.commands[command]; @@ -88,481 +490,67 @@ function exec(command, editor, args) { if (editor && editor.$readOnly && !command.readOnly) return false; - if (!command.multiCursor) { + if (!command.multiSelectAction) { command.exec(editor, args || {}); - } else if (command.multiCursor == "forEach") { - forEachSelection(editor, command, args) - } else if (command.multiCursor == "single") { - exitMultiSelectMode(editor); + } else if (command.multiSelectAction == "forEach") { + editor.forEachSelection(command, args); + } else if (command.multiSelectAction == "single") { + editor.exitMultiSelectMode(); command.exec(editor, args || {}); } else { - command.multiCursor(editor, args || {}); + command.multiSelectAction(editor, args || {}); } return true; }; -function enterMultiSelectMode(editor) { - if (editor.inMultiSelectMode) - return - editor.inMultiSelectMode = true - editor.setStyle("multiselect") - editor.keyBinding.addKeyboardHandler(exports.keyboardHandler); - editor.commands.__exec = editor.commands.exec - editor.commands.exec = exec - editor.session.$undoSelect = false - editor.selection.rangeList.attach(editor.session); -} -function exitMultiSelectMode(editor) { - if (editor.session.multiSelection.inVirtualMode) - return - editor.inMultiSelectMode = false; - editor.selection.secondarySelections = []; - editor.unsetStyle("multiselect"); - editor.selection.rangeList.removeAll(); - editor.keyBinding.removeKeyboardHandler(exports.keyboardHandler); - - editor.commands.exec = editor.commands.__exec; - editor.renderer.updateCursor(); - editor.renderer.updateBackMarkers(); - - editor.session.$undoSelect = true - - editor.selection.rangeList.detach(editor.session); -} - -function initSession(session) { - if (session.selection.rangeList) - return - session.multiSelection = session.selection - - var rangeList = new RangeList; - - rangeList.all = []; - rangeList.on("add", function(e) { - rangeList.all.unshift(e.range) - }) - - rangeList.on("remove", function(e) { - var ranges = e.ranges - for (var i = ranges.length; i--; ) { - var index = rangeList.all.indexOf(ranges[i]); - rangeList.all.splice(index, 1); - } - }); - - session.selection.rangeList = rangeList; - session.selection.cursor = session.selection.selectionLead; - session.selection.secondarySelections = []; - session.selection.getAllRanges = function() { - return this.rangeList.ranges.concat(this.secondarySelections) - }; - session.selection.rangeCount = 1; -} - -var addSelectionRange = function(editor, orientedRange) { - if (!editor.inMultiSelectMode) - enterMultiSelectMode(editor) - - if (!orientedRange.cursor) - orientedRange.cursor = orientedRange.end - - var style = editor.getSelectionStyle(); - orientedRange.marker = editor.session.addMarker(orientedRange, "ace_selection", style); - - // use this to not conflict with virtualSelections added by forEachSelection - var selection = editor.session.multiSelection; - selection.rangeList.add(orientedRange); - selection.rangeCount = selection.rangeList.all.length + selection.secondarySelections.length; - - selection.fromOrientedRange(orientedRange) - editor.renderer.updateCursor(); - editor.renderer.updateBackMarkers(); -}; - -function addCursorV(editor, dir){ - var range = editor.selection.getRange() - var isBackwards = editor.selection.isBackwards() - range.cursor = isBackwards ? range.start : range.end; - - var screenLead = editor.session.documentToScreenPosition(range.cursor); - if (editor.selection.$desiredColumn) - screenLead.column = editor.selection.$desiredColumn; - - var lead = editor.session.screenToDocumentPosition(screenLead.row + dir, screenLead.column); - - if (!range.isEmpty()) { - var screenAnchor = editor.session.documentToScreenPosition(isBackwards ? range.end : range.start); - var anchor = editor.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 (!editor.inMultiSelectMode) { - addSelectionRange(editor, range) - } else { - var allRanges = editor.selection.rangeList.ranges - // remove range if at end - if (range.isEequal(allRanges[dir == 1 ? 0 : allRanges.length - 1])) - var toRemove = range.cursor - } - addSelectionRange(editor, newRange) - if (toRemove) - editor.selection.rangeList.substractPoint(toRemove) -} - -function transposeSelections(editor, dir) { - var session = editor.session; - var sel = session.multiSelection; - var all = sel.rangeList.all; - - 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(editor.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; - } -} - -function splitIntoLines(editor) { - var sel = editor.session.multiSelection - if (sel.rangeCount > 1) { - var ranges = sel.rangeList.ranges; - var lastRange = ranges[ranges.length - 1] - var range = Range.fromPoints(ranges[0].start, lastRange.end) - - exitMultiSelectMode(editor) - sel.setSelectionRange(range, lastRange.cursor == lastRange.start) - } else { - - } -} - -var Search = require("ace/search").Search -var search = new Search - -function find(session, needle, dir) { - search.$options.wrap = false; - search.$options.needle = needle; - search.$options.backwards = dir == -1; - return search.find(session) -} -function addRange(editor, dir, skip) { - var session = editor.session; - var sel = session.multiSelection; - var all = sel.rangeList.all; - - var range = sel.getRange(); - if (range.isEmpty()) { - var tmp = session.getWordRange(range.start.row, range.start.column) - var offset = tmp.start.column - range.start.column - range = tmp; - } - var needle = session.getTextRange(range); - - if (skip) { - - } - - var newRange = find(session, needle, dir); - - newRange.cursor = dir == -1 ? newRange.start : newRange.end; - addSelectionRange(editor, newRange) -} -// commands -// add multicursor annotations to default commands -var defaultCommands = require("./commands/default_commands").commands; - -defaultCommands.forEach(function(command) { - var single = RegExp(["selectall"].join("|"), ""); - var mapOverCommands = RegExp(["backspace", "del", - "golinedown", "golineup", "gotoend", "gotoleft", "gotolineend", "gotolinestart", - "gotoright", "gotostart", "gotowordleft", "gotowordright", - "indent", "insertstring", "inserttext", "jumptomatching", "outdent", - "removetolineend", "removetolinestart", "removewordleft", "removewordright", - "selectdown", "selectleft", "selectlineend", "selectlinestart", "selectright", - "selecttoend", "selecttolineend", "selecttolinestart", "selecttostart", - "selectup", "selectwordleft", "selectwordright", - "splitline", "tolowercase", "touppercase"].join("|"), ""); - - if (single.test(command.name)) - command.multiCursor = "single"; - else if (mapOverCommands.test(command.name)) - command.multiCursor = "forEach"; - else if (command.name == "transposeletters") - command.multiCursor = transposeSelections -}); - -// commands to to enter multicursor mode -exports.defaultCommands = [{ - name: "addCursorAbove", - exec: function(editor) {addCursorV(editor, -1); }, - bindKey: {win: "Alt-Shift-Up", mac: "Alt-Shift-Up"} -}, { - name: "addCursorBelow", - exec: function(editor) {addCursorV(editor, 1); }, - bindKey: {win: "Alt-Shift-Down", mac: "Alt-Shift-Down"} -}, { - name: "selectMoreBefore", - exec: function(editor) {addRange(editor, -1); }, - bindKey: {win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up"} -}, { - name: "selectMoreAfter", - exec: function(editor) {addRange(editor, 1); }, - bindKey: {win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down"} -}, { - name: "selectNextBefore", - exec: function(editor) {addCursorV(editor, -1, true); }, - bindKey: {win: "Ctrl-Shift-PageUp", mac: "Ctrl-Shift-PageUp"} -}, { - name: "selectNextAfter", - exec: function(editor) {addCursorV(editor, 1, true); }, - bindKey: {win: "Ctrl-Shift-PageDown", mac: "Ctrl-Shift-PageDown"} -}, { - name: "splitIntoLines", - exec: function(editor) {splitIntoLines(editor); }, - bindKey: {win: "Ctrl-Shift-L", mac: "Ctrl-Shift-L"} -}, ]; - -// commands active when multiple cursors are present -exports.multiEditCommands = [{ - name: "singleSelection", - bindKey: "esc", - exec: function(editor) { - console.log(editor) - exitMultiSelectMode(editor) - }, -}]; - -var HashHandler = require("ace/keyboard/hash_handler").HashHandler; -exports.keyboardHandler = new HashHandler(exports.multiEditCommands); // 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 = ev.ctrlKey; - var button = e.getButton(); - if (!ctrl && !alt) { - if (e.editor.selection.rangeCount > 1) { - if (button == 0) { - exitMultiSelectMode(e.editor) - } 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; - } +// 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 editor = e.editor; - var selection = editor.selection; - var isMultiSelect = selection.rangeCount > 1 - var pos = e.getDocumentPosition(); - var rangeList = selection.rangeList; - var cursor = selection.getCursor() - var inSelection = e.inSelection() || (selection.isEmpty() && isSamePoint(pos, cursor)); + var oldSession = e.oldSession; + if (oldSession) { + // todo use events + if (oldSession.multiSelect && oldSession.multiSelect.editor == this) + oldSession.multiSelect.editor = null; - if (ctrl && !shift && !alt && button == 0) { - if (!isMultiSelect && inSelection) - return // dragging + session.multiSelect.removeEventListener("addRange", this.$onAddRange); + session.multiSelect.removeEventListener("removeRange", this.$onRemoveRange); + session.multiSelect.removeEventListener("multiSelect", this.$onMultiSelect); + session.multiSelect.removeEventListener("singleSelect", this.$onSingleSelect); + } - if (!isMultiSelect) { - addSelectionRange(editor, selection.toOrientedRange()) - } - if (inSelection) - selection.clearSelection(); - - selection.secondarySelections.push(selection) - selection.rangeCount++ - var oldRange = rangeList.substractPoint(pos) - - event.capture(editor.container, function(){}, function() { - var i = selection.secondarySelections.indexOf(selection); - if (i != -1) { - selection.rangeCount-- - selection.secondarySelections.splice(i, 1); - } - - var tmpSel = selection.toOrientedRange(); - - if (oldRange && oldRange.isEmpty() && tmpSel.isEequal(oldRange)) { - var range = selection.rangeList.all[0] - editor.selection.rangeList._emit("remove", {ranges: []}) - selection.setSelectionRange(range, range.cursor == range.start) - selection._emit("changeSelection"); - selection._emit("changeCursor"); - return; - } - - addSelectionRange(editor, tmpSel) - }); - - //e.stop() - } else if (!shift && alt && button == 0) { - e.stop() - var mouseX = e.pageX, mouseY = e.pageY; - var onMouseSelection = function(e) { - mouseX = event.getDocumentX(e); - mouseY = event.getDocumentY(e); - }; - - selection.moveCursorToPosition(pos); - selection.clearSelection(); - if (!isMultiSelect) { - enterMultiSelectMode(editor) - selection.rangeCount = Infinity - } - - - var rectSel = [] - selection.secondarySelections = rectSel - - var session = editor.session - var style = editor.getSelectionStyle(); - function addMarker(range) { - range.marker = session.addMarker(range, "ace_selection", style); - } - function removeMarker(range) { - session.removeMarker(range.marker); - } - - - - var onMouseSelectionEnd = function(e) { - clearInterval(timerId); - rectSel.forEach(removeMarker) - selection.secondarySelections = []; - for (var i = rectSel.length; i--; ) - addSelectionRange(editor, rectSel[i]) - - if (selection.rangeCount == Infinity) { - selection.rangeCount = selection.rangeList.all.length + selection.secondarySelections.length; - - if (selection.rangeCount <= 1) - exitMultiSelectMode(editor) - } - }; - - var anchor = selection.getCursor(); - var screenAnchor = session.documentToScreenPosition(anchor); - var screenCursor = screenAnchor - var screenLength = session.getScreenLength() - 1 - - var onSelectionInterval = function() { - var newCursor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY); - if (isSamePoint(screenCursor, newCursor)) - return - rectSel.forEach(removeMarker) - rectSel.splice(0, rectSel.length) - screenCursor = newCursor - 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 (endRow > screenLength) - endRow = screenLength - - for (var row = startRow; row <= endRow; row++) { - var r = Range.fromPoints( - session.screenToDocumentPosition(row, startColumn), - session.screenToDocumentPosition(row, endColumn) - ) - r.cursor = xBackwards ? r.start : r.end - rectSel.push(r) - } - - rectSel.forEach(addMarker) - var lastIndex = yBackwards ? 0 : rectSel.length - 1 - selection.moveCursorToPosition(rectSel[lastIndex].cursor); - selection.clearSelection() - editor.renderer.scrollCursorIntoView(); - - editor.renderer.updateCursor(); - editor.renderer.updateBackMarkers(); - }; - - event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); - var timerId = setInterval(onSelectionInterval, 20); - - return e.preventDefault(); - } + session.multiSelect.on("addRange", this.$onAddRange); + session.multiSelect.on("removeRange", this.$onRemoveRange); + session.multiSelect.on("multiSelect", this.$onMultiSelect); + session.multiSelect.on("singleSelect", this.$onSingleSelect); } -// MultiCursor +// adds multicursor support to editor instance function MultiSelect(editor) { - initSession(editor.session); - editor.on("changeSession", function(e) { - initSession(e.session) - }.bind(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); - editor.selection.rangeList.on("remove", function(e) { - var ranges = e.ranges; - for (var i = ranges.length; i--; ) { - var range = ranges[i]; - if (range.marker != null) - this.session.removeMarker(range.marker); - - this.rangeCount --; - } - - if (this.rangeCount == 1 && editor.inMultiSelectMode) - exitMultiSelectMode(editor) - }.bind(editor.selection)); + exports.onSessionChange.call(editor, editor); + editor.on("changeSession", exports.onSessionChange.bind(editor)); editor.on("mousedown", onMouseDown); - editor.commands.addCommands(exports.defaultCommands); + editor.commands.addCommands(exports.commands.defaultCommands); } diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js index 9777b5ab..f30b1756 100644 --- a/lib/ace/range_list.js +++ b/lib/ace/range_list.js @@ -85,9 +85,9 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { endIndex++; var removed = this.ranges.splice(startIndex, endIndex - startIndex, range); - this._emit("add", {range: range}); if (removed.length) this._emit("remove", {ranges: removed}); + this._emit("add", {range: range}); return startIndex; }; @@ -143,6 +143,12 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { 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; From a3f61f711a6ae83d5a2c10149f0be644f65aff9e Mon Sep 17 00:00:00 2001 From: nightwing Date: Sat, 31 Mar 2012 22:08:08 +0400 Subject: [PATCH 24/33] add some tests --- lib/ace/multi_select.js | 40 ++-- lib/ace/multi_select_test.js | 144 ++++++++++++++ lib/ace/test/all_browser.js | 1 + lib/ace/test/mockrenderer.js | 376 ++++++++++++++++++----------------- 4 files changed, 350 insertions(+), 211 deletions(-) create mode 100644 lib/ace/multi_select_test.js diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 3b5a8bd0..4890e79c 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -41,11 +41,10 @@ define(function(require, exports, module) { var RangeList = require("./range_list").RangeList; var Range = require("./range").Range; var Selection = require("./selection").Selection; -var Range = require("./range").Range; var onMouseDown = require("./mouse/multi_select_mouse_handler").onMouseDown; exports.commands = require("./commands/multi_select_commands"); - +// Todo var Search = require("ace/search").Search var search = new Search @@ -82,6 +81,8 @@ var EditSession = require("./edit_session").EditSession; if (this.rangeCount > 1 && !this.inMultiSelectMode) { this._emit("multiSelect"); this.inMultiSelectMode = true; + this.$undoSelect = false; + this.rangeList.attach(this.session); } this.fromOrientedRange(range); @@ -102,7 +103,9 @@ var EditSession = require("./edit_session").EditSession; if (this.rangeCount <= 1 && this.inMultiSelectMode) { this.inMultiSelectMode = false; this._emit("singleSelect"); - + this.$undoSelect = true; + this.rangeList.detach(this.session); + if (this.rangeCount == 1) this.single(); } @@ -135,22 +138,7 @@ var EditSession = require("./edit_session").EditSession; this.rangeList.on("remove", this.$onRemoveRange.bind(this)); }; this.getAllRanges = function() { - return this.rangeList.ranges.concat(this.secondarySelections) - }; - - - 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.single() - this.setSelectionRange(range, lastRange.cursor == lastRange.start) - } else { - var cursor = this.getRange().toScreenRange(); - range - } + return this.rangeList.ranges.concat() }; this.splitIntoLines = function () { @@ -284,9 +272,6 @@ var Editor = require("./editor").Editor; this.commands.exec = exports.exec; this.renderer.updateCursor(); this.renderer.updateBackMarkers(); - - this.session.$undoSelect = false; - this.selection.rangeList.attach(this.session); }; this.$onSingleSelect = function(e) { @@ -300,10 +285,6 @@ var Editor = require("./editor").Editor; this.commands.exec = this.commands.__SingleSelectionExec; this.renderer.updateCursor(); this.renderer.updateBackMarkers(); - - this.session.$undoSelect = true; - - this.selection.rangeList.detach(this.session); }; this.forEachSelection = function(cmd, args) { @@ -537,6 +518,13 @@ exports.onSessionChange = function(e) { session.multiSelect.on("removeRange", this.$onRemoveRange); session.multiSelect.on("multiSelect", this.$onMultiSelect); session.multiSelect.on("singleSelect", this.$onSingleSelect); + + if (this.inMultiSelectMode != session.selection.inMultiSelectMode) { + if (session.selection.inMultiSelectMode) + this.$onMultiSelect(); + else + this.$onSingleSelect(); + } } // adds multicursor support to editor instance diff --git a/lib/ace/multi_select_test.js b/lib/ace/multi_select_test.js new file mode 100644 index 00000000..5c9d0dfe --- /dev/null +++ b/lib/ace/multi_select_test.js @@ -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 + * + * 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("ace/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() +} \ No newline at end of file diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index b9484a86..d36d2a9c 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -41,6 +41,7 @@ var testNames = [ "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", diff --git a/lib/ace/test/mockrenderer.js b/lib/ace/test/mockrenderer.js index 5efac2a9..67708735 100644 --- a/lib/ace/test/mockrenderer.js +++ b/lib/ace/test/mockrenderer.js @@ -1,185 +1,191 @@ -/* ***** 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 - * - * 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 MockRenderer = exports.MockRenderer = function(visibleRowCount) { - this.container = document.createElement("div"); - this.visibleRowCount = visibleRowCount || 20; - - this.layerConfig = { - firstVisibleRow : 0, - lastVisibleRow : this.visibleRowCount - }; - - this.isMockRenderer = true; - - this.$gutter = {}; -}; - - -MockRenderer.prototype.getFirstVisibleRow = function() { - return this.layerConfig.firstVisibleRow; -}; - -MockRenderer.prototype.getLastVisibleRow = function() { - return this.layerConfig.lastVisibleRow; -}; - -MockRenderer.prototype.getFirstFullyVisibleRow = function() { - return this.layerConfig.firstVisibleRow; -}; - -MockRenderer.prototype.getLastFullyVisibleRow = function() { - return this.layerConfig.lastVisibleRow; -}; - -MockRenderer.prototype.getContainerElement = function() { - return this.container; -}; - -MockRenderer.prototype.getMouseEventTarget = function() { - return this.container; -}; - -MockRenderer.prototype.getTextAreaContainer = function() { - return this.container; -}; - -MockRenderer.prototype.moveTextAreaToCursor = function() { -}; - -MockRenderer.prototype.setSession = function(session) { - this.session = session; -}; - -MockRenderer.prototype.getSession = function(session) { - return this.session; -}; - -MockRenderer.prototype.setTokenizer = function() { -}; - -MockRenderer.prototype.on = function() { -}; - -MockRenderer.prototype.updateCursor = function() { -}; - -MockRenderer.prototype.scrollToX = function(scrollTop) {}; -MockRenderer.prototype.scrollToY = function(scrollLeft) {}; - -MockRenderer.prototype.scrollToLine = function(line, center) { - var lineHeight = { lineHeight: 16 }; - var row = 0; - for (var l = 1; l < line; l++) { - row += this.session.getRowHeight(lineHeight, l-1) / lineHeight.lineHeight; - } - - if (center) { - row -= this.visibleRowCount / 2; - } - this.scrollToRow(row); -}; - -MockRenderer.prototype.scrollCursorIntoView = function() { - var cursor = this.session.getSelection().getCursor(); - if (cursor.row < this.layerConfig.firstVisibleRow) { - this.scrollToRow(cursor.row); - } - else if (cursor.row > this.layerConfig.lastVisibleRow) { - this.scrollToRow(cursor.row); - } -}; - -MockRenderer.prototype.scrollToRow = function(row) { - var row = Math.min(this.session.getLength() - this.visibleRowCount, Math.max(0, - row)); - this.layerConfig.firstVisibleRow = row; - this.layerConfig.lastVisibleRow = row + this.visibleRowCount; -}; - -MockRenderer.prototype.getScrollTopRow = function() { - return this.layerConfig.firstVisibleRow; -}; - -MockRenderer.prototype.draw = function() { -}; - -MockRenderer.prototype.updateLines = function(startRow, endRow) { -}; - -MockRenderer.prototype.updateBackMarkers = function() { -}; - -MockRenderer.prototype.updateFrontMarkers = function() { -}; - -MockRenderer.prototype.setBreakpoints = function() { -}; - -MockRenderer.prototype.onResize = function() { -}; - -MockRenderer.prototype.updateFull = function() { -}; - -MockRenderer.prototype.updateText = function() { -}; - -MockRenderer.prototype.showCursor = function() { -}; - -MockRenderer.prototype.visualizeFocus = function() { -}; - -MockRenderer.prototype.setAnnotations = function() { -}; - -MockRenderer.prototype.textToScreenCoordinates = function() { - return { - pageX: 0, - pageY: 0 - } -}; - -MockRenderer.prototype.adjustWrapLimit = function () { - -}; - -}); +/* ***** 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 + * + * 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 MockRenderer = exports.MockRenderer = function(visibleRowCount) { + this.container = document.createElement("div"); + this.visibleRowCount = visibleRowCount || 20; + + this.layerConfig = { + firstVisibleRow : 0, + lastVisibleRow : this.visibleRowCount + }; + + this.isMockRenderer = true; + + this.$gutter = {}; +}; + + +MockRenderer.prototype.getFirstVisibleRow = function() { + return this.layerConfig.firstVisibleRow; +}; + +MockRenderer.prototype.getLastVisibleRow = function() { + return this.layerConfig.lastVisibleRow; +}; + +MockRenderer.prototype.getFirstFullyVisibleRow = function() { + return this.layerConfig.firstVisibleRow; +}; + +MockRenderer.prototype.getLastFullyVisibleRow = function() { + return this.layerConfig.lastVisibleRow; +}; + +MockRenderer.prototype.getContainerElement = function() { + return this.container; +}; + +MockRenderer.prototype.getMouseEventTarget = function() { + return this.container; +}; + +MockRenderer.prototype.getTextAreaContainer = function() { + return this.container; +}; + +MockRenderer.prototype.moveTextAreaToCursor = function() { +}; + +MockRenderer.prototype.setSession = function(session) { + this.session = session; +}; + +MockRenderer.prototype.getSession = function(session) { + return this.session; +}; + +MockRenderer.prototype.setTokenizer = function() { +}; + +MockRenderer.prototype.on = function() { +}; + +MockRenderer.prototype.updateCursor = function() { +}; + +MockRenderer.prototype.scrollToX = function(scrollTop) {}; +MockRenderer.prototype.scrollToY = function(scrollLeft) {}; + +MockRenderer.prototype.scrollToLine = function(line, center) { + var lineHeight = { lineHeight: 16 }; + var row = 0; + for (var l = 1; l < line; l++) { + row += this.session.getRowHeight(lineHeight, l-1) / lineHeight.lineHeight; + } + + if (center) { + row -= this.visibleRowCount / 2; + } + this.scrollToRow(row); +}; + +MockRenderer.prototype.scrollCursorIntoView = function() { + var cursor = this.session.getSelection().getCursor(); + if (cursor.row < this.layerConfig.firstVisibleRow) { + this.scrollToRow(cursor.row); + } + else if (cursor.row > this.layerConfig.lastVisibleRow) { + this.scrollToRow(cursor.row); + } +}; + +MockRenderer.prototype.scrollToRow = function(row) { + var row = Math.min(this.session.getLength() - this.visibleRowCount, Math.max(0, + row)); + this.layerConfig.firstVisibleRow = row; + this.layerConfig.lastVisibleRow = row + this.visibleRowCount; +}; + +MockRenderer.prototype.getScrollTopRow = function() { + return this.layerConfig.firstVisibleRow; +}; + +MockRenderer.prototype.draw = function() { +}; + +MockRenderer.prototype.updateLines = function(startRow, endRow) { +}; + +MockRenderer.prototype.updateBackMarkers = function() { +}; + +MockRenderer.prototype.updateFrontMarkers = function() { +}; + +MockRenderer.prototype.setBreakpoints = function() { +}; + +MockRenderer.prototype.onResize = function() { +}; + +MockRenderer.prototype.updateFull = function() { +}; + +MockRenderer.prototype.updateText = function() { +}; + +MockRenderer.prototype.showCursor = function() { +}; + +MockRenderer.prototype.visualizeFocus = function() { +}; + +MockRenderer.prototype.setAnnotations = function() { +}; + +MockRenderer.prototype.setStyle = function() { +}; + +MockRenderer.prototype.unsetStyle = function() { +}; + +MockRenderer.prototype.textToScreenCoordinates = function() { + return { + pageX: 0, + pageY: 0 + } +}; + +MockRenderer.prototype.adjustWrapLimit = function () { + +}; + +}); From 299cdc1b19ec95559d75f1719ebccd15346ef728 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 3 Apr 2012 00:00:44 +0400 Subject: [PATCH 25/33] fix typo --- lib/ace/edit_session/folding.js | 2 +- lib/ace/range.js | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index 36565555..d6a08102 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -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; } diff --git a/lib/ace/range.js b/lib/ace/range.js index 16e88f9c..163979fe 100644 --- a/lib/ace/range.js +++ b/lib/ace/range.js @@ -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); }; From 346ae096b3cda42c9c848c31c9c828e555e10e2d Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 3 Apr 2012 00:06:07 +0400 Subject: [PATCH 26/33] overuse of events is bad:( --- lib/ace/commands/multi_select_commands.js | 60 +- ...use_handler.js => multi_select_handler.js} | 131 +-- lib/ace/multi_select.js | 785 +++++++++--------- lib/ace/range_list.js | 101 ++- lib/ace/range_list_test.js | 7 +- 5 files changed, 581 insertions(+), 503 deletions(-) rename lib/ace/mouse/{multi_select_mouse_handler.js => multi_select_handler.js} (56%) diff --git a/lib/ace/commands/multi_select_commands.js b/lib/ace/commands/multi_select_commands.js index 3ec6778b..f2627a8f 100644 --- a/lib/ace/commands/multi_select_commands.js +++ b/lib/ace/commands/multi_select_commands.js @@ -40,23 +40,23 @@ 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", - "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); } + 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", + "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 @@ -64,37 +64,47 @@ exports.defaultCommands = [{ name: "addCursorAbove", exec: function(editor) { editor.selectMoreLines(-1); }, bindKey: {win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up"}, - readonly: true + readonly: true }, { name: "addCursorBelow", exec: function(editor) { editor.selectMoreLines(1); }, bindKey: {win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down"}, - readonly: true + 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 + readonly: true }, { name: "selectMoreAfter", exec: function(editor) { editor.selectMore(1); }, bindKey: {win: "Ctrl-Alt-Right", mac: "Ctrl-Alt-Right"}, - readonly: true + readonly: true }, { name: "selectNextBefore", exec: function(editor) { editor.selectMore(-1, true); }, bindKey: {win: "Ctrl-Alt-Shift-Left", mac: "Ctrl-Alt-Shift-Left"}, - readonly: true + readonly: true }, { name: "selectNextAfter", exec: function(editor) { editor.selectMore(1, true); }, bindKey: {win: "Ctrl-Alt-Shift-Right", mac: "Ctrl-Alt-Shift-Right"}, - readonly: true + readonly: true }, { name: "splitIntoLines", exec: function(editor) { editor.multiSelect.splitIntoLines(); }, bindKey: {win: "Ctrl-Shift-L", mac: "Ctrl-Shift-L"}, - readonly: true + readonly: true }, ]; // commands active in multiselect mode @@ -102,7 +112,7 @@ exports.multiEditCommands = [{ name: "singleSelection", bindKey: "esc", exec: function(editor) { editor.exitMultiSelectMode(); }, - readonly: true + readonly: true }]; var HashHandler = require("../keyboard/hash_handler").HashHandler; diff --git a/lib/ace/mouse/multi_select_mouse_handler.js b/lib/ace/mouse/multi_select_handler.js similarity index 56% rename from lib/ace/mouse/multi_select_mouse_handler.js rename to lib/ace/mouse/multi_select_handler.js index 5d8dcfd5..318d78ae 100644 --- a/lib/ace/mouse/multi_select_mouse_handler.js +++ b/lib/ace/mouse/multi_select_handler.js @@ -50,11 +50,11 @@ function onMouseDown(e) { var ev = e.domEvent; var alt = ev.altKey; var shift = ev.shiftKey; - var ctrl = ev.ctrlKey; + var ctrl = e.getAccelKey(); var button = e.getButton(); if (!ctrl && !alt) { - if (e.editor.selection.rangeCount > 1) { + if (e.editor.inMultiSelectMode) { if (button == 0) { e.editor.exitMultiSelectMode(); } else if (button == 2) { @@ -70,12 +70,71 @@ function onMouseDown(e) { var editor = e.editor; var selection = editor.selection; - var isMultiSelect = selection.rangeCount > 1 + var isMultiSelect = editor.inMultiSelectMode; var pos = e.getDocumentPosition(); - var rangeList = selection.rangeList; 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 normalSelect = function() { + var clickSelection + if (_self.$clickSelection) { + if (_self.$clickSelection.contains(cursor.row, cursor.column)) { + editor.selection.setSelectionRange(_self.$clickSelection); + } + else { + if (_self.$clickSelection.compare(cursor.row, cursor.column) == -1) { + anchor = _self.$clickSelection.end; + } + else { + anchor = _self.$clickSelection.start; + } + editor.selection.setSelectionAnchor(anchor.row, anchor.column); + editor.selection.selectToPosition(cursor); + } + } + else { + editor.selection.selectToPosition(cursor); + } + + editor.renderer.scrollCursorIntoView(); + }; + + var session = editor.session; + var anchor = selection.getCursor(); + var screenAnchor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY); + var clippedAnchor = session.documentToScreenPosition(anchor); + var screenCursor = screenAnchor; + + + onSelectionInterval = blockSelect; + if (ctrl && !shift && !alt && button == 0) { if (!isMultiSelect && inSelection) return // dragging @@ -86,23 +145,23 @@ function onMouseDown(e) { if (inSelection) selection.clearSelection(); - var helper = selection.toOrientedRange(); - editor.addSelectionMarker(helper); + var helper = selection.toOrientedRange(); + editor.addSelectionMarker(helper); - var oldRange = rangeList.rangeAtPoint(pos); + var oldRange = selection.rangeList.rangeAtPoint(pos); event.capture(editor.container, function(){}, function() { editor.removeSelectionMarkers([helper]); var tmpSel = selection.toOrientedRange(); if (oldRange && tmpSel.isEmpty() && isSamePoint(oldRange.cursor, tmpSel.cursor)) { - if (selection.rangeCount > 1) { - range = editor.selection.rangeList.substractPoint(tmpSel.cursor); - var range = editor.selection.rangeList.all[0]; - if (range) - editor.selection.addRange(range); - return; - } + if (selection.rangeCount > 1) { + editor.selection.substractPoint(tmpSel.cursor); + var range = editor.selection.ranges[0]; + if (range) + editor.selection.addRange(range); + return; + } } selection.addRange(tmpSel); @@ -111,20 +170,18 @@ function onMouseDown(e) { //e.stop() } else if (!shift && alt && button == 0) { e.stop() - var mouseX = e.pageX, mouseY = e.pageY; - var onMouseSelection = function(e) { - mouseX = event.getDocumentX(e); - mouseY = event.getDocumentY(e); - }; - if (isMultiSelect && !ctrl) { - selection.single(); + + if (isMultiSelect && !ctrl) { + selection.toSingleRange(); + } else if (!isMultiSelect && ctrl) { + selection.addRange(); } - selection.moveCursorToPosition(pos); - selection.clearSelection(); + + selection.moveCursorToPosition(pos); + selection.clearSelection(); var rectSel = []; - var session = editor.session; var onMouseSelectionEnd = function(e) { clearInterval(timerId); @@ -133,34 +190,12 @@ function onMouseDown(e) { selection.addRange(rectSel[i]) }; - var anchor = selection.getCursor(); - var screenAnchor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY); - var clippedAnchor = session.documentToScreenPosition(anchor); - var screenCursor = screenAnchor; + - var onSelectionInterval = 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.renderer.updateCursor(); - editor.renderer.updateBackMarkers(); - }; event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); - var timerId = setInterval(onSelectionInterval, 20); + var timerId = setInterval(function() {onSelectionInterval()}, 20); return e.preventDefault(); } diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 4890e79c..12d4569b 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -41,7 +41,7 @@ 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_mouse_handler").onMouseDown; +var onMouseDown = require("./mouse/multi_select_handler").onMouseDown; exports.commands = require("./commands/multi_select_commands"); // Todo @@ -49,292 +49,330 @@ var Search = require("ace/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) + 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; - }; + this.getSelectionMarkers = function() { + return this.$selectionMarkers; + }; }).call(EditSession.prototype); // extend Selection ;(function() { - this.addRange = function(range) { - if (!range.cursor) - range.cursor = range.end; - - if (this.rangeCount == 0) { - var oldRange = this.toOrientedRange(); - this.rangeList.add(oldRange); - this._emit("addRange", {range: oldRange}); - } + // list of ranges in reverse addition order + this.ranges = null; + // automatically sorted list of ranges + this.rangeList = null; - this.rangeList.add(range); - this.rangeCount = this.rangeList.ranges.length; - - if (this.rangeCount > 1 && !this.inMultiSelectMode) { - this._emit("multiSelect"); - this.inMultiSelectMode = true; - this.$undoSelect = false; - this.rangeList.attach(this.session); - } + /** + * 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); + } + } - this.fromOrientedRange(range); - if (this.rangeCount >= 1) - this._emit("addRange", {range: range}); - }; + if (!range) + return; - this.single = function(range) { - range = range || this.rangeList.all[0]; - this.rangeList.removeAll(); - range && this.fromOrientedRange(range); - }; + if (!range.cursor) + range.cursor = range.end; - this.$onRemoveRange = function(e) { - this.rangeCount = this.rangeList.ranges.length; - this._emit("removeRange", e); + var removed = this.rangeList.add(range); - if (this.rangeCount <= 1 && this.inMultiSelectMode) { - this.inMultiSelectMode = false; - this._emit("singleSelect"); - this.$undoSelect = true; - this.rangeList.detach(this.session); + this.fromOrientedRange(range); + this.$onAddRange(range); - if (this.rangeCount == 1) - this.single(); - } - }; + if (removed.length) + this.$onRemoveRange(removed); - // adds multicursor support to selection - this.$initRangeList = function() { - if (this.rangeList) - return; + if (this.rangeCount > 0 && !this.inMultiSelectMode) { + this._emit("multiSelect"); + this.inMultiSelectMode = true; + this.session.$undoSelect = false; + this.rangeList.attach(this.session); + } + }; - var rangeList = new RangeList; - // list of ranges in reverse addition order - // rangeList.all[0] is the same as selection.getRange - rangeList.all = []; - rangeList.on("add", function(e) { - rangeList.all.unshift(e.range) - }) - rangeList.on("remove", function(e) { - var ranges = e.ranges - for (var i = ranges.length; i--; ) { - var index = rangeList.all.indexOf(ranges[i]); - rangeList.all.splice(index, 1); - } - }); + this.toSingleRange = function(range) { + range = range || this.ranges[0]; + var removed = this.rangeList.removeAll(); + if (removed.length) + this.$onRemoveRange(removed); - this.rangeList = rangeList; - this.cursor = this.selectionLead; + range && this.fromOrientedRange(range); + }; - this.rangeCount = 1; - this.rangeList.on("remove", this.$onRemoveRange.bind(this)); - }; - this.getAllRanges = function() { - return this.rangeList.ranges.concat() - }; + this.substractPoint = function(pos) { + var removed = this.rangeList.substractPoint(pos); + if (removed) { + this.$onRemoveRange(removed); + return removed[0] + } + }; - 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.mergeOverlappingRanges = function() { + var removed = this.rangeList.merge(); + var lastRange = this.ranges[0]; + if (removed.length) + this.$onRemoveRange(removed); - this.single() - 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); - } - }; - - this.rectangularRangeBlock = function(screenCursor, screenAnchor, includeEmptyLines) { - var rectSel = []; + if (lastRange) + this.fromOrientedRange(lastRange); + }; - 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; + this.$onAddRange = function(range) { + this.rangeCount = this.rangeList.ranges.length; + this.ranges.unshift(range); + this._emit("addRange", {range: range}); + }; - 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; - }; + this.$onRemoveRange = function(removed) { + this.rangeCount = this.rangeList.ranges.length; + if (this.rangeCount == 1 && this.inMultiSelectMode) { + removed.push(this.rangeList.ranges.pop()); + this.rangeCount = 0; + } + + for (var i = removed.length; i--; ) { + var index = this.ranges.indexOf(removed[i]); + this.ranges.splice(index, 1); + } + + console.log(this.ranges+'') + 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); + } + }; + + // 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.addSelectionMarker = function(orientedRange) { - if (!orientedRange.cursor) - orientedRange.cursor = orientedRange.end; + this.updateSelectionMarkers = function(orientedRange) { + 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); + 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.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.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.$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); - // FixMe - this.commands.__SingleSelectionExec = this.commands.exec; - this.commands.exec = exports.exec; - this.renderer.updateCursor(); - this.renderer.updateBackMarkers(); - }; + this.setStyle("multiselect"); + this.keyBinding.addKeyboardHandler(exports.commands.keyboardHandler); + // FixMe + this.commands.__SingleSelectionExec = this.commands.exec; + this.commands.exec = exports.exec; + this.renderer.updateCursor(); + this.renderer.updateBackMarkers(); + }; - this.$onSingleSelect = function(e) { - if (this.session.multiSelect.inVirtualMode) - return; - this.inMultiSelectMode = false; + this.$onSingleSelect = function(e) { + if (this.session.multiSelect.inVirtualMode) + return; + this.inMultiSelectMode = false; - this.unsetStyle("multiselect"); - this.keyBinding.removeKeyboardHandler(exports.commands.keyboardHandler); + this.unsetStyle("multiselect"); + this.keyBinding.removeKeyboardHandler(exports.commands.keyboardHandler); - this.commands.exec = this.commands.__SingleSelectionExec; - this.renderer.updateCursor(); - this.renderer.updateBackMarkers(); - }; + this.commands.exec = this.commands.__SingleSelectionExec; + this.renderer.updateCursor(); + this.renderer.updateBackMarkers(); + }; - this.forEachSelection = function(cmd, args) { - if (this.inVirtualSelectionMode) - return; - var session = this.session - var selection = this.selection - var rangeList = selection.rangeList + 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 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(); + 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; - rangeList.merge(); + this.selection = session.selection = selection; + this.inVirtualSelectionMode = false; + selection._eventRegistry = reg; + selection.mergeOverlappingRanges(); - var lastRange = selection.rangeList.all[0] - lastRange && selection.fromOrientedRange(lastRange); + this.onCursorChange(); + this.onSelectionChange(); + }; - selection._emit("changeSelection") - selection._emit("changeCursor") - this.renderer.updateCursor(); - this.renderer.updateBackMarkers(); - }; + this.exitMultiSelectMode = function() { + if (this.inVirtualSelectionMode) + return; + this.multiSelect.toSingleRange(); + }; - this.exitMultiSelectMode = function() { - if (this.inVirtualSelectionMode) - return; - this.multiSelect.single(); - }; - - // todo route copy/cut/paste through commandmanager - this.getCopyText = function() { + // todo route copy/cut/paste through commandmanager + this.getCopyText = function() { var text = ""; - if (this.inMultiSelectMode) { - var ranges = this.multiSelect.rangeList.ranges; - for (var i = 0; i < ranges.length; i++) { - text += this.session.getTextRange(ranges[i]); - } + if (this.inMultiSelectMode) { + var ranges = this.multiSelect.rangeList.ranges; + for (var i = 0; i < ranges.length; i++) { + text += this.session.getTextRange(ranges[i]); + } } else if (!this.selection.isEmpty()) text = this.session.getTextRange(this.getSelectionRange()); @@ -342,120 +380,121 @@ var Editor = require("./editor").Editor; return text; }; - this.onCut = function() { + this.onCut = function() { var cmd = { - name: "cut", - exec: function(editor) { - var range = editor.getSelectionRange(); - editor._emit("cut", range); + 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) + if (!editor.selection.isEmpty()) { + editor.session.remove(range); + editor.clearSelection(); + } + }, + readonly: true, + multiSelectAction: "forEach" + } + this.commands.exec(cmd, this) }; - // commands + // commands - this.selectMoreLines = function(dir, skip) { - var range = this.selection.toOrientedRange(); - var isBackwards = range.cursor == range.end; + 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 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); + 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 (!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; - } + 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 - if (skip || range.isEequal(allRanges[dir == 1 ? 0 : allRanges.length - 1])) - var toRemove = range.cursor; - } + 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.rangeList.substractPoint(toRemove); - } + 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.rangeList.all; + 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; - } + 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()); + 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; - } - } + 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 all = sel.rangeList.all; + this.selectMore = function (dir, skip) { + var session = this.session; + var sel = session.multiSelect; + var all = sel.ranges; - 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 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.rangeList.substractPoint(range.cursor); - } + 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); @@ -476,11 +515,11 @@ exports.exec = function(command, editor, args) { } else if (command.multiSelectAction == "forEach") { editor.forEachSelection(command, args); } else if (command.multiSelectAction == "single") { - editor.exitMultiSelectMode(); + editor.exitMultiSelectMode(); command.exec(editor, args || {}); - } else { + } else { command.multiSelectAction(editor, args || {}); - } + } return true; }; @@ -494,45 +533,47 @@ function isSamePoint(p1, p2) { // 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; - } + 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; + 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.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); - - if (this.inMultiSelectMode != session.selection.inMultiSelectMode) { - if (session.selection.inMultiSelectMode) - this.$onMultiSelect(); - else - 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); + 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)); diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js index f30b1756..c3dc2dd0 100644 --- a/lib/ace/range_list.js +++ b/lib/ace/range_list.js @@ -46,8 +46,6 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { }; (function() { - oop.implement(this, EventEmitter); - this.comparePoints = function(p1, p2) { return p1.row - p2.row || p1.column - p2.column }; @@ -84,25 +82,22 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { else endIndex++; - var removed = this.ranges.splice(startIndex, endIndex - startIndex, range); - if (removed.length) - this._emit("remove", {ranges: removed}); - this._emit("add", {range: range}); - return startIndex; + return this.ranges.splice(startIndex, endIndex - startIndex, range); }; this.addList = function(list) { - list.forEach(this.add, this); + 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) { - var removed = this.ranges.splice(i, 1); - this._emit("remove", {ranges: removed}); - return removed[0] - } + if (i >= 0) + return this.ranges.splice(i, 1); }; // merge overlapping ranges @@ -131,8 +126,7 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { i--; } - if (removed.length) - this._emit("remove", {ranges: removed}); + return removed; }; this.contains = function(row, column) { @@ -170,8 +164,7 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { }; this.removeAll = function() { - if (this.ranges.length) - this._emit("remove", {ranges: this.ranges.splice(0, this.ranges.length)}); + return this.ranges.splice(0, this.ranges.length); }; this.attach = function(session) { @@ -193,45 +186,45 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { 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; + 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; - 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; + var colDiff = -start.column + end.column; - 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; - } - } + 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); diff --git a/lib/ace/range_list_test.js b/lib/ace/range_list_test.js index a73202f5..6c397369 100644 --- a/lib/ace/range_list_test.js +++ b/lib/ace/range_list_test.js @@ -124,25 +124,24 @@ module.exports = { new Range(8,8,9,9) ]); var removed = []; - rangeList.on('remove', function(e) {removed = e.ranges}); assert.equal(rangeList.ranges.length, 4); rangeList.ranges[1].end.row = 7; - rangeList.merge(); + 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; - rangeList.merge(); + 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)); - rangeList.merge(); + removed = rangeList.merge(); assert.equal(rangeList.ranges.length, 2); }, From 6cde5f3b4f8b43af797b28f85334db8bdf68653f Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 3 Apr 2012 00:14:23 +0400 Subject: [PATCH 27/33] cleanup --- lib/ace/commands/multi_select_commands.js | 2 +- lib/ace/edit_session/fold.js | 2 +- lib/ace/mouse/fold_handler.js | 2 +- lib/ace/mouse/multi_select_handler.js | 60 +++-------------------- lib/ace/multi_select.js | 21 ++++---- lib/ace/range_list.js | 4 +- 6 files changed, 25 insertions(+), 66 deletions(-) diff --git a/lib/ace/commands/multi_select_commands.js b/lib/ace/commands/multi_select_commands.js index f2627a8f..0c5223b2 100644 --- a/lib/ace/commands/multi_select_commands.js +++ b/lib/ace/commands/multi_select_commands.js @@ -20,7 +20,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Fabian Jakobs + * Harutyun Amirjanyan * * 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 diff --git a/lib/ace/edit_session/fold.js b/lib/ace/edit_session/fold.js index d15bc025..8ce5e708 100644 --- a/lib/ace/edit_session/fold.js +++ b/lib/ace/edit_session/fold.js @@ -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)) diff --git a/lib/ace/mouse/fold_handler.js b/lib/ace/mouse/fold_handler.js index 41232024..986ca934 100644 --- a/lib/ace/mouse/fold_handler.js +++ b/lib/ace/mouse/fold_handler.js @@ -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()) diff --git a/lib/ace/mouse/multi_select_handler.js b/lib/ace/mouse/multi_select_handler.js index 318d78ae..96211cac 100644 --- a/lib/ace/mouse/multi_select_handler.js +++ b/lib/ace/mouse/multi_select_handler.js @@ -102,30 +102,6 @@ function onMouseDown(e) { editor.updateSelectionMarkers() }; - var normalSelect = function() { - var clickSelection - if (_self.$clickSelection) { - if (_self.$clickSelection.contains(cursor.row, cursor.column)) { - editor.selection.setSelectionRange(_self.$clickSelection); - } - else { - if (_self.$clickSelection.compare(cursor.row, cursor.column) == -1) { - anchor = _self.$clickSelection.end; - } - else { - anchor = _self.$clickSelection.start; - } - editor.selection.setSelectionAnchor(anchor.row, anchor.column); - editor.selection.selectToPosition(cursor); - } - } - else { - editor.selection.selectToPosition(cursor); - } - - editor.renderer.scrollCursorIntoView(); - }; - var session = editor.session; var anchor = selection.getCursor(); var screenAnchor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY); @@ -133,50 +109,33 @@ function onMouseDown(e) { var screenCursor = screenAnchor; - onSelectionInterval = blockSelect; if (ctrl && !shift && !alt && button == 0) { if (!isMultiSelect && inSelection) return // dragging - if (!isMultiSelect) { + if (!isMultiSelect) selection.addRange(selection.toOrientedRange()); - } - if (inSelection) - selection.clearSelection(); - var helper = selection.toOrientedRange(); - editor.addSelectionMarker(helper); var oldRange = selection.rangeList.rangeAtPoint(pos); event.capture(editor.container, function(){}, function() { - editor.removeSelectionMarkers([helper]); var tmpSel = selection.toOrientedRange(); - if (oldRange && tmpSel.isEmpty() && isSamePoint(oldRange.cursor, tmpSel.cursor)) { - if (selection.rangeCount > 1) { - editor.selection.substractPoint(tmpSel.cursor); - var range = editor.selection.ranges[0]; - if (range) - editor.selection.addRange(range); - return; - } - } - - selection.addRange(tmpSel); + if (oldRange && tmpSel.isEmpty() && isSamePoint(oldRange.cursor, tmpSel.cursor)) + selection.substractPoint(tmpSel.cursor); + else + selection.addRange(tmpSel); }); - //e.stop() } else if (!shift && alt && button == 0) { e.stop() - - if (isMultiSelect && !ctrl) { + if (isMultiSelect && !ctrl) selection.toSingleRange(); - } else if (!isMultiSelect && ctrl) { + else if (!isMultiSelect && ctrl) selection.addRange(); - } selection.moveCursorToPosition(pos); selection.clearSelection(); @@ -190,9 +149,7 @@ function onMouseDown(e) { selection.addRange(rectSel[i]) }; - - - + onSelectionInterval = blockSelect; event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); var timerId = setInterval(function() {onSelectionInterval()}, 20); @@ -202,7 +159,6 @@ function onMouseDown(e) { } - exports.onMouseDown = onMouseDown; }); \ No newline at end of file diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 12d4569b..27cf79b6 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -90,7 +90,6 @@ var EditSession = require("./edit_session").EditSession; var removed = this.rangeList.add(range); - this.fromOrientedRange(range); this.$onAddRange(range); if (removed.length) @@ -123,24 +122,24 @@ var EditSession = require("./edit_session").EditSession; this.mergeOverlappingRanges = function() { var removed = this.rangeList.merge(); - var lastRange = this.ranges[0]; if (removed.length) this.$onRemoveRange(removed); - - if (lastRange) - this.fromOrientedRange(lastRange); + else + 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) { - removed.push(this.rangeList.ranges.pop()); + var lastRange = this.rangeList.ranges.pop() + removed.push(lastRange); this.rangeCount = 0; } @@ -149,7 +148,6 @@ var EditSession = require("./edit_session").EditSession; this.ranges.splice(index, 1); } - console.log(this.ranges+'') this._emit("removeRange", {ranges: removed}); if (this.rangeCount == 0 && this.inMultiSelectMode) { @@ -158,6 +156,10 @@ var EditSession = require("./edit_session").EditSession; 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 @@ -448,9 +450,9 @@ var Editor = require("./editor").Editor; var words = []; for (var i = all.length; i--; ) { - var range = all[i] + var range = all[i]; if (range.isEmpty()) { - var tmp = session.getWordRange(range.start.row, range.start.column) + 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; @@ -459,6 +461,7 @@ var Editor = require("./editor").Editor; words.unshift(this.session.getTextRange(range)); } + if (dir < 0) words.unshift(words.pop()); else diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js index c3dc2dd0..565bf5e1 100644 --- a/lib/ace/range_list.js +++ b/lib/ace/range_list.js @@ -19,7 +19,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Fabian Jakobs + * Harutyun Amirjanyan * * 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 @@ -71,7 +71,7 @@ var RangeList = function(startRow, startColumn, endRow, endColumn) { }; this.add = function(range) { - var startIndex = this.pointIndex(range.start); + var startIndex = this.pointIndex(range.start); if (startIndex < 0) startIndex = -startIndex - 1; From 587e391bb552f53c78adfae916bb9c3bdc6c10a9 Mon Sep 17 00:00:00 2001 From: nightwing Date: Wed, 4 Apr 2012 13:06:42 +0400 Subject: [PATCH 28/33] fix failing test --- lib/ace/virtual_renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 385854e8..dab4c68b 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -842,7 +842,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; From 20215367940fc008ba5e6f783b9a23e90ceec75e Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 8 Apr 2012 11:46:20 +0400 Subject: [PATCH 29/33] emit event before executing a command --- lib/ace/commands/command_manager.js | 22 +++++--- lib/ace/commands/multi_select_commands.js | 2 +- lib/ace/multi_select.js | 61 ++++++++++------------- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/lib/ace/commands/command_manager.js b/lib/ace/commands/command_manager.js index 94945dcf..428562d7 100644 --- a/lib/ace/commands/command_manager.js +++ b/lib/ace/commands/command_manager.js @@ -3,6 +3,7 @@ define(function(require, exports, module) { var oop = require("../lib/oop"); var HashHandler = require("../keyboard/hash_handler").HashHandler; +var EventEmitter = require("../lib/event_emitter").EventEmitter; var CommandManager = function(platform, commands) { this.platform = platform; @@ -10,12 +11,17 @@ var CommandManager = function(platform, commands) { this.commmandKeyBinding = {}; this.addCommands(commands); + + this.setDefaultHandler("exec", function(e) { + e.command.exec(e.editor, e.args || {}) + }); }; oop.inherits(CommandManager, HashHandler); (function() { + oop.implement(this, EventEmitter); this.exec = function(command, editor, args) { if (typeof command === 'string') @@ -27,7 +33,7 @@ oop.inherits(CommandManager, HashHandler); if (editor && editor.$readOnly && !command.readOnly) return false; - command.exec(editor, args || {}); + this._emit("exec", {editor: editor, command: command, args: args}); return true; }; @@ -36,20 +42,22 @@ oop.inherits(CommandManager, HashHandler); 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; }; diff --git a/lib/ace/commands/multi_select_commands.js b/lib/ace/commands/multi_select_commands.js index 0c5223b2..f120069a 100644 --- a/lib/ace/commands/multi_select_commands.js +++ b/lib/ace/commands/multi_select_commands.js @@ -45,7 +45,7 @@ require("./default_commands").commands.forEach(function(command) { "golinedown", "golineup", "gotoend", "gotoleft", "gotolineend", "gotolinestart", "gotoright", "gotostart", "gotowordleft", "gotowordright", "indent", "insertstring", "inserttext", "jumptomatching", "outdent", - "removetolineend", "removetolinestart", "removewordleft", "removewordright", + "removetolineend", "removetolinestart", "removewordleft", "removewordright", "removeline", "selectdown", "selectleft", "selectlineend", "selectlinestart", "selectright", "selecttoend", "selecttolineend", "selecttolinestart", "selecttostart", "selectup", "selectwordleft", "selectwordright", diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 27cf79b6..fcafc7b7 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -124,14 +124,14 @@ var EditSession = require("./edit_session").EditSession; var removed = this.rangeList.merge(); if (removed.length) this.$onRemoveRange(removed); - else - this.fromOrientedRange(this.ranges[0]); + 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.fromOrientedRange(range); this._emit("addRange", {range: range}); }; @@ -269,7 +269,7 @@ var Editor = require("./editor").Editor; this.renderer.updateCursor(); this.renderer.updateBackMarkers(); }; - + this.addSelectionMarker = function(orientedRange) { if (!orientedRange.cursor) orientedRange.cursor = orientedRange.end; @@ -312,13 +312,11 @@ var Editor = require("./editor").Editor; this.setStyle("multiselect"); this.keyBinding.addKeyboardHandler(exports.commands.keyboardHandler); - // FixMe - this.commands.__SingleSelectionExec = this.commands.exec; - this.commands.exec = exports.exec; + this.commands.on("exec", this.$onMultiSelectExec); + this.renderer.updateCursor(); this.renderer.updateBackMarkers(); }; - this.$onSingleSelect = function(e) { if (this.session.multiSelect.inVirtualMode) return; @@ -327,11 +325,29 @@ var Editor = require("./editor").Editor; this.unsetStyle("multiselect"); this.keyBinding.removeKeyboardHandler(exports.commands.keyboardHandler); - this.commands.exec = this.commands.__SingleSelectionExec; + 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.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; @@ -499,36 +515,9 @@ var Editor = require("./editor").Editor; this.multiSelect.substractPoint(range.cursor); } - }).call(Editor.prototype); -// Todo emit event before exec? -exports.exec = function(command, editor, args) { - if (typeof command === 'string') - command = this.commands[command]; - if (!command) - return false; - - if (editor && editor.$readOnly && !command.readOnly) - return false; - - if (!command.multiSelectAction) { - command.exec(editor, args || {}); - } else if (command.multiSelectAction == "forEach") { - editor.forEachSelection(command, args); - } else if (command.multiSelectAction == "single") { - editor.exitMultiSelectMode(); - command.exec(editor, args || {}); - } else { - command.multiSelectAction(editor, args || {}); - } - return true; -}; - - - -// mouse function isSamePoint(p1, p2) { return p1.row == p2.row && p1.column == p2.column } From 94eddc1362653ea707b53a36997599443a059e0d Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 11 Apr 2012 16:55:27 +0200 Subject: [PATCH 30/33] some cleanups. Tabs to spaces, etc --- lib/ace/multi_select.js | 12 ++-- lib/ace/multi_select_test.js | 108 +++++++++++++++++------------------ 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index fcafc7b7..b26dcbde 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -45,8 +45,8 @@ var onMouseDown = require("./mouse/multi_select_handler").onMouseDown; exports.commands = require("./commands/multi_select_commands"); // Todo -var Search = require("ace/search").Search -var search = new Search +var Search = require("./search").Search; +var search = new Search(); function find(session, needle, dir) { search.$options.wrap = true; @@ -67,6 +67,7 @@ var EditSession = require("./edit_session").EditSession; ;(function() { // list of ranges in reverse addition order this.ranges = null; + // automatically sorted list of ranges this.rangeList = null; @@ -391,9 +392,9 @@ var Editor = require("./editor").Editor; for (var i = 0; i < ranges.length; i++) { text += this.session.getTextRange(ranges[i]); } - } else if (!this.selection.isEmpty()) + } else if (!this.selection.isEmpty()) { text = this.session.getTextRange(this.getSelectionRange()); - + } return text; }; @@ -514,7 +515,6 @@ var Editor = require("./editor").Editor; if (skip) this.multiSelect.substractPoint(range.cursor); } - }).call(Editor.prototype); @@ -574,8 +574,6 @@ function MultiSelect(editor) { editor.commands.addCommands(exports.commands.defaultCommands); } - - exports.MultiSelect = MultiSelect; }); \ No newline at end of file diff --git a/lib/ace/multi_select_test.js b/lib/ace/multi_select_test.js index 5c9d0dfe..1f6a6f26 100644 --- a/lib/ace/multi_select_test.js +++ b/lib/ace/multi_select_test.js @@ -48,16 +48,16 @@ var Editor = require("./editor").Editor; var MockRenderer = require("./test/mockrenderer").MockRenderer; var Range = require("./range").Range; var assert = require("./test/assertions"); -var MultiSelect = require("ace/multi_select").MultiSelect; +var MultiSelect = require("./multi_select").MultiSelect; var editor var exec = function(name, times, args) { - do { - editor.commands.exec(name, editor, args); - } while(times --> 1) + do { + editor.commands.exec(name, editor, args); + } while(times --> 1) }; var testRanges = function(str) { - assert.equal(editor.selection.getAllRanges()+"", str); + assert.equal(editor.selection.getAllRanges()+"", str); } module.exports = { @@ -66,74 +66,74 @@ module.exports = { "test: multiselect editing": function() { var doc = new EditSession([ - "w1.w2", - " wtt.w", - " wtt.w" - ]); + "w1.w2", + " wtt.w", + " wtt.w" + ]); editor = new Editor(new MockRenderer(), doc); - MultiSelect(editor); + MultiSelect(editor); - editor.navigateFileEnd(); - exec("selectMoreBefore", 3); - assert.ok(editor.inMultiSelectMode); - assert.equal(editor.selection.getAllRanges().length, 4); + 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.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); + assert.equal(editor.selection.getAllRanges().length, 4); - exec("selectall"); - assert.ok(!editor.inMultiSelectMode); - //assert.equal(editor.selection.getAllRanges().length, 1); + exec("selectall"); + assert.ok(!editor.inMultiSelectMode); + //assert.equal(editor.selection.getAllRanges().length, 1); }, - "test: multiselect navigation": function() { + "test: multiselect navigation": function() { var doc = new EditSession([ - "w1.w2", - " wtt.w", - " wtt.we" - ]); + "w1.w2", + " wtt.w", + " wtt.we" + ]); editor = new Editor(new MockRenderer(), doc); - MultiSelect(editor); + MultiSelect(editor); - editor.selectMoreLines(1); - testRanges("Range: [0/0] -> [0/0],Range: [1/0] -> [1/0]"); - assert.ok(editor.inMultiSelectMode); + 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"); + 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); + testRanges("Range: [1/8] -> [1/9],Range: [2/8] -> [2/10]"); + exec("golinedown", 2); + assert.ok(!editor.inMultiSelectMode); }, - "test: multiselect session change": function() { + "test: multiselect session change": function() { var doc = new EditSession([ - "w1.w2", - " wtt.w", - " wtt.w" - ]); + "w1.w2", + " wtt.w", + " wtt.w" + ]); var editor = new Editor(new MockRenderer(), doc); - MultiSelect(editor); + MultiSelect(editor); - editor.selectMoreLines(1) - assert.equal( - editor.selection.getAllRanges()+"", - "Range: [0/0] -> [0/0],Range: [1/0] -> [1/0]" - ); - assert.ok(editor.inMultiSelectMode); + 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); + var doc2 = new EditSession(["w1"]); + editor.setSession(doc2); + assert.ok(!editor.inMultiSelectMode); - editor.setSession(doc); - assert.ok(editor.inMultiSelectMode); + editor.setSession(doc); + assert.ok(editor.inMultiSelectMode); } }; From ca35f324c847ba0b8f5e5f505258d5af41755cfd Mon Sep 17 00:00:00 2001 From: nightwing Date: Wed, 11 Apr 2012 19:31:43 +0400 Subject: [PATCH 31/33] small cleanup --- lib/ace/multi_select.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index fcafc7b7..2dc6e33c 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -44,15 +44,15 @@ var Selection = require("./selection").Selection; var onMouseDown = require("./mouse/multi_select_handler").onMouseDown; exports.commands = require("./commands/multi_select_commands"); -// Todo -var Search = require("ace/search").Search -var search = new Search +// 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) + return search.find(session); } // extend EditSession @@ -335,6 +335,7 @@ var Editor = require("./editor").Editor; 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); From 8c7b03012887e916c0f514b8281a892933da127d Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 12 Apr 2012 15:33:04 +0200 Subject: [PATCH 32/33] cleanups --- lib/ace/multi_select.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 36c99a8b..b82524a6 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -168,23 +168,23 @@ var EditSession = require("./edit_session").EditSession; if (this.rangeList) return; - this.rangeList = new RangeList; + this.rangeList = new RangeList(); this.ranges = []; this.rangeCount = 0; }; this.getAllRanges = function() { - return this.rangeList.ranges.concat() + 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) + var lastRange = ranges[ranges.length - 1]; + var range = Range.fromPoints(ranges[0].start, lastRange.end); - this.toSingleRange() - this.setSelectionRange(range, lastRange.cursor == lastRange.start) + this.toSingleRange(); + this.setSelectionRange(range, lastRange.cursor == lastRange.start); } else { var cursor = this.session.documentToScreenPosition(this.selectionLead); var anchor = this.session.documentToScreenPosition(this.selectionAnchor); From b34d25f82df56401dd393173a49e188ed91eba8c Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 12 Apr 2012 15:59:45 +0200 Subject: [PATCH 33/33] small cleanups --- lib/ace/commands/command_manager.js | 2 +- lib/ace/commands/multi_select_commands.js | 4 ++-- lib/ace/multi_select.js | 22 ++++++++++++---------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/ace/commands/command_manager.js b/lib/ace/commands/command_manager.js index 428562d7..380611eb 100644 --- a/lib/ace/commands/command_manager.js +++ b/lib/ace/commands/command_manager.js @@ -13,7 +13,7 @@ var CommandManager = function(platform, commands) { this.addCommands(commands); this.setDefaultHandler("exec", function(e) { - e.command.exec(e.editor, e.args || {}) + e.command.exec(e.editor, e.args || {}); }); }; diff --git a/lib/ace/commands/multi_select_commands.js b/lib/ace/commands/multi_select_commands.js index f120069a..7ca053c6 100644 --- a/lib/ace/commands/multi_select_commands.js +++ b/lib/ace/commands/multi_select_commands.js @@ -56,7 +56,7 @@ require("./default_commands").commands.forEach(function(command) { else if (mapOver.test(command.name)) command.multiSelectAction = "forEach"; else if (command.name == "transposeletters") - command.multiSelectAction = function(editor) {editor.transposeSelections(1); } + command.multiSelectAction = function(editor) {editor.transposeSelections(1); }; }); // commands to enter multiselect mode @@ -105,7 +105,7 @@ exports.defaultCommands = [{ exec: function(editor) { editor.multiSelect.splitIntoLines(); }, bindKey: {win: "Ctrl-Shift-L", mac: "Ctrl-Shift-L"}, readonly: true -}, ]; +}]; // commands active in multiselect mode exports.multiEditCommands = [{ diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index b82524a6..634310bc 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -266,7 +266,7 @@ var EditSession = require("./edit_session").EditSession; // extend Editor var Editor = require("./editor").Editor; ;(function() { - this.updateSelectionMarkers = function(orientedRange) { + this.updateSelectionMarkers = function() { this.renderer.updateCursor(); this.renderer.updateBackMarkers(); }; @@ -353,9 +353,10 @@ var Editor = require("./editor").Editor; this.forEachSelection = function(cmd, args) { if (this.inVirtualSelectionMode) return; - var session = this.session - var selection = this.selection - var rangeList = selection.rangeList + + var session = this.session; + var selection = this.selection; + var rangeList = selection.rangeList; var reg = selection._eventRegistry; selection._eventRegistry = {}; @@ -390,9 +391,11 @@ var Editor = require("./editor").Editor; var text = ""; if (this.inMultiSelectMode) { var ranges = this.multiSelect.rangeList.ranges; + text = []; for (var i = 0; i < ranges.length; i++) { - text += this.session.getTextRange(ranges[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()); } @@ -414,8 +417,8 @@ var Editor = require("./editor").Editor; }, readonly: true, multiSelectAction: "forEach" - } - this.commands.exec(cmd, this) + }; + this.commands.exec(cmd, this); }; // commands @@ -500,13 +503,12 @@ var Editor = require("./editor").Editor; var range = sel.toOrientedRange(); if (range.isEmpty()) { - var range = session.getWordRange(range.start.row, range.start.column) + 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; @@ -558,7 +560,7 @@ exports.onSessionChange = function(e) { else this.$onSingleSelect(); } -} +}; // adds multicursor support to editor instance function MultiSelect(editor) {