From 90034c3a44d6da0d56feb918b6b148f04ea7af60 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Fri, 21 Mar 2014 22:41:46 -0700 Subject: [PATCH 01/11] fixing multi selection handling for various emacs commands Conflicts: lib/ace/keyboard/emacs.js --- lib/ace/keyboard/emacs.js | 65 ++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index ab4471b2..fd533f1d 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -318,7 +318,8 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { exec: function(editor, args) { for (var i = 0; i < count; i++) command.exec(editor, args); - } + }, + multiSelectAction: command.multiSelectAction } }; } else { @@ -439,37 +440,39 @@ exports.handler.addCommands({ // selection modification commands. That is, // "goto" commands become "select" commands. // Any insertion or mouse click resets mark-mode. - // setMark twice in a row at the same place resets markmode + // setMark twice in a row at the same place resets markmode. + // in multi select mode, ea selection is handled individually + if (args && args.count) { - var mark = editor.popEmacsMark(); - mark && editor.selection.moveCursorToPosition(mark); + function moveToMark() { + var mark = editor.popEmacsMark(); + mark && editor.moveCursorToPosition(mark); + } + if (editor.inMultiSelectMode) editor.forEachSelection({exec: moveToMark}); + else moveToMark(); return; } var mark = editor.emacsMark(), - transientMarkModeActive = true; - + ranges = editor.selection.getAllRanges(), + rangePositions = ranges.map(function(r) { return {row: r.start.row, column: r.start.column}; }), + transientMarkModeActive = true, + hasNoSelection = ranges.every(function(range) { return range.isEmpty(); }); // if transientMarkModeActive then mark behavior is a little // different. Deactivate the mark when setMark is run with active // mark - if (transientMarkModeActive && (mark || !editor.selection.isEmpty())) { - editor.pushEmacsMark(); - editor.clearSelection(); + if (transientMarkModeActive && (mark || !hasNoSelection)) { + if (editor.inMultiSelectMode) editor.forEachSelection({exec: editor.clearSelection.bind(editor)}) + else editor.clearSelection(); + if (mark) editor.pushEmacsMark(null); return; } - if (mark) { - var cp = editor.getCursorPosition(); - if (editor.selection.isEmpty() && - mark.row == cp.row && mark.column == cp.column) { - editor.pushEmacsMark(); - return; - } + if (!mark) { + rangePositions.slice(0,-1).forEach(function(pos) { editor.pushEmacsMark(pos); }); + editor.setEmacsMark(rangePositions[rangePositions.length-1]); + return; } - // turn on mark mode - mark = editor.getCursorPosition(); - editor.setEmacsMark(mark); - editor.selection.setSelectionAnchor(mark.row, mark.column); }, readOnly: true, handlesCount: true, @@ -544,6 +547,7 @@ exports.handler.addCommands({ if (editor.keyBinding.$data.lastCommand != "yank") return; editor.undo(); + editor.session.$emacsMarkRing.pop(); // also undo recording mark editor.onPaste(exports.killRing.rotate()); editor.keyBinding.$data.lastCommand = "yank"; }, @@ -557,12 +561,25 @@ exports.handler.addCommands({ }, killRingSave: { exec: function(editor) { + // copy text and deselect. will save marks for starts of the + // selection(s) + + editor.$handlesEmacsOnCopy = true; + var marks = editor.session.$emacsMarkRing.slice(), + deselectedMarks = []; exports.killRing.add(editor.getCopyText()); + setTimeout(function() { - var sel = editor.selection, - range = sel.getRange(); - editor.pushEmacsMark(sel.isBackwards() ? range.end : range.start); - sel.clearSelection(); + function deselect() { + var sel = editor.selection, range = sel.getRange(), + pos = sel.isBackwards() ? range.end : range.start; + deselectedMarks.push({row: pos.row, column: pos.column}); + sel.clearSelection(); + } + editor.$handlesEmacsOnCopy = false; + if (editor.inMultiSelectMode) editor.forEachSelection({exec: deselect}); + else deselect(); + editor.session.$emacsMarkRing = marks.concat(deselectedMarks.reverse()); }, 0); }, readOnly: true From 24f3bab19d570bf74919e6801ad0afe296f5d92b Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Sat, 22 Mar 2014 01:11:23 -0700 Subject: [PATCH 02/11] emacs: fix multi select exchangePointAndMark Conflicts: lib/ace/keyboard/emacs.js --- lib/ace/keyboard/emacs.js | 42 +++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index fd533f1d..38e29f96 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -442,7 +442,7 @@ exports.handler.addCommands({ // Any insertion or mouse click resets mark-mode. // setMark twice in a row at the same place resets markmode. // in multi select mode, ea selection is handled individually - + if (args && args.count) { function moveToMark() { var mark = editor.popEmacsMark(); @@ -452,7 +452,7 @@ exports.handler.addCommands({ else moveToMark(); return; } - + var mark = editor.emacsMark(), ranges = editor.selection.getAllRanges(), rangePositions = ranges.map(function(r) { return {row: r.start.row, column: r.start.column}; }), @@ -467,7 +467,7 @@ exports.handler.addCommands({ if (mark) editor.pushEmacsMark(null); return; } - + if (!mark) { rangePositions.slice(0,-1).forEach(function(pos) { editor.pushEmacsMark(pos); }); editor.setEmacsMark(rangePositions[rangePositions.length-1]); @@ -479,22 +479,26 @@ exports.handler.addCommands({ multiSelectAction: "forEach" }, exchangePointAndMark: { - exec: function(editor, args) { - var sel = editor.selection; - if (args.count) { - var pos = editor.getCursorPosition(); - sel.clearSelection(); - sel.moveCursorToPosition(editor.popEmacsMark()); - editor.pushEmacsMark(pos); - return; - } - var lastMark = editor.getLastEmacsMark(); - var range = sel.getRange(); - if (range.isEmpty()) { - sel.selectToPosition(lastMark); - return; - } - sel.setSelectionRange(range, !sel.isBackwards()); + exec: function (editor, args) { + var restoreMarks = []; + editor.forEachSelection({ + exec: function() { + var sel = editor.selection; + if (args.count) { // replace mark and point + var pos = {row: sel.lead.row, column: sel.lead.column}; + restoreMarks.push(pos); + sel.clearSelection(); + sel.moveCursorToPosition(editor.popEmacsMark()); + } else if (sel.isEmpty()) { // move to mark, forget point + var lastMark = editor.popEmacsMark(); + restoreMarks.push(lastMark); + sel.selectToPosition(lastMark); + } else { // just invert selection + sel.setSelectionRange(sel.getRange(), !sel.isBackwards()); + } + } + }); + restoreMarks.reverse().forEach(function(p) { editor.pushEmacsMark(p); }); }, readOnly: true, handlesCount: true, From b966cfd7da9ea0a00f9a9a64038be46e6dc22f3c Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Sat, 22 Mar 2014 01:11:23 -0700 Subject: [PATCH 03/11] emacs: cleanup --- lib/ace/keyboard/emacs.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 38e29f96..7c88af38 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -442,7 +442,7 @@ exports.handler.addCommands({ // Any insertion or mouse click resets mark-mode. // setMark twice in a row at the same place resets markmode. // in multi select mode, ea selection is handled individually - + if (args && args.count) { function moveToMark() { var mark = editor.popEmacsMark(); @@ -452,7 +452,7 @@ exports.handler.addCommands({ else moveToMark(); return; } - + var mark = editor.emacsMark(), ranges = editor.selection.getAllRanges(), rangePositions = ranges.map(function(r) { return {row: r.start.row, column: r.start.column}; }), @@ -467,7 +467,7 @@ exports.handler.addCommands({ if (mark) editor.pushEmacsMark(null); return; } - + if (!mark) { rangePositions.slice(0,-1).forEach(function(pos) { editor.pushEmacsMark(pos); }); editor.setEmacsMark(rangePositions[rangePositions.length-1]); From 78b2147c3c9cf5744863ea6db9273d6be05834a3 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Mon, 24 Mar 2014 16:15:59 -0700 Subject: [PATCH 04/11] emacs/isearch: select when moving cursor via isearch and emacs mark is set --- lib/ace/incremental_search.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ace/incremental_search.js b/lib/ace/incremental_search.js index 88615006..ee74f60d 100644 --- a/lib/ace/incremental_search.js +++ b/lib/ace/incremental_search.js @@ -140,10 +140,11 @@ oop.inherits(IncrementalSearch, Search); // try to find the next occurence and enable highlighting marker options.start = this.$currentPos; var session = this.$editor.session, - found = this.find(session); + found = this.find(session), + shouldSelect = !!this.$editor.emacsMark(); if (found) { if (options.backwards) found = Range.fromPoints(found.end, found.start); - this.$editor.moveCursorToPosition(found.end); + this.$editor.selection.setRange(Range.fromPoints(shouldSelect ? this.$startPos : found.end, found.end)); if (moveToNext) this.$currentPos = found.end; // highlight after cursor move, so selection works properly this.highlight(options.re) From ea67201540eedf6dd2347e9fa9c1d0fd6d738789 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Mon, 24 Mar 2014 16:16:54 -0700 Subject: [PATCH 05/11] emacs: fix exchangePointAndMark in normal select mode --- lib/ace/keyboard/emacs.js | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 7c88af38..b1543894 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -481,24 +481,25 @@ exports.handler.addCommands({ exchangePointAndMark: { exec: function (editor, args) { var restoreMarks = []; - editor.forEachSelection({ - exec: function() { - var sel = editor.selection; - if (args.count) { // replace mark and point - var pos = {row: sel.lead.row, column: sel.lead.column}; - restoreMarks.push(pos); - sel.clearSelection(); - sel.moveCursorToPosition(editor.popEmacsMark()); - } else if (sel.isEmpty()) { // move to mark, forget point - var lastMark = editor.popEmacsMark(); - restoreMarks.push(lastMark); - sel.selectToPosition(lastMark); - } else { // just invert selection - sel.setSelectionRange(sel.getRange(), !sel.isBackwards()); - } - } - }); + if (editor.inMultiSelectMode) editor.forEachSelection({exec: doExchange}); + else doExchange(); restoreMarks.reverse().forEach(function(p) { editor.pushEmacsMark(p); }); + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + function doExchange() { + var sel = editor.selection; + if (args.count) { // replace mark and point + var pos = {row: sel.lead.row, column: sel.lead.column}; + restoreMarks.push(pos); + sel.clearSelection(); + sel.moveCursorToPosition(editor.popEmacsMark()); + } else if (sel.isEmpty()) { // move to mark, forget point + var lastMark = editor.popEmacsMark(); + restoreMarks.push(lastMark); + sel.selectToPosition(lastMark); + } else { // just invert selection + sel.setSelectionRange(sel.getRange(), !sel.isBackwards()); + } + } }, readOnly: true, handlesCount: true, From 54d80d79ab723f0b5f040f72d2df9d15c2b4724a Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 4 Nov 2014 13:01:49 -0800 Subject: [PATCH 06/11] emacs: make code strict conform --- lib/ace/keyboard/emacs.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index b1543894..272071d1 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -444,10 +444,6 @@ exports.handler.addCommands({ // in multi select mode, ea selection is handled individually if (args && args.count) { - function moveToMark() { - var mark = editor.popEmacsMark(); - mark && editor.moveCursorToPosition(mark); - } if (editor.inMultiSelectMode) editor.forEachSelection({exec: moveToMark}); else moveToMark(); return; @@ -473,6 +469,14 @@ exports.handler.addCommands({ editor.setEmacsMark(rangePositions[rangePositions.length-1]); return; } + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + function moveToMark() { + var mark = editor.popEmacsMark(); + mark && editor.moveCursorToPosition(mark); + } + }, readOnly: true, handlesCount: true, From 0a055cb7259dcc697a0a2a4820293201895c9314 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 4 Nov 2014 21:10:14 -0800 Subject: [PATCH 07/11] isearch: being flexible about whether emacs mode is on or not --- lib/ace/incremental_search.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ace/incremental_search.js b/lib/ace/incremental_search.js index ee74f60d..0f32e0c1 100644 --- a/lib/ace/incremental_search.js +++ b/lib/ace/incremental_search.js @@ -141,7 +141,8 @@ oop.inherits(IncrementalSearch, Search); options.start = this.$currentPos; var session = this.$editor.session, found = this.find(session), - shouldSelect = !!this.$editor.emacsMark(); + shouldSelect = this.$editor.emacsMark ? + !!this.$editor.emacsMark() : !this.$editor.selection.isEmpty(); if (found) { if (options.backwards) found = Range.fromPoints(found.end, found.start); this.$editor.selection.setRange(Range.fromPoints(shouldSelect ? this.$startPos : found.end, found.end)); From 8786e17e6b235446d45095be3c9aead8bc17bd61 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 4 Nov 2014 21:23:45 -0800 Subject: [PATCH 08/11] isearch test: ensure multi select --- lib/ace/incremental_search_test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ace/incremental_search_test.js b/lib/ace/incremental_search_test.js index 262e09a7..f47e6b4f 100644 --- a/lib/ace/incremental_search_test.js +++ b/lib/ace/incremental_search_test.js @@ -35,10 +35,12 @@ if (typeof process !== "undefined") { define(function(require, exports, module) { "use strict"; +var emacs = require('./keyboard/emacs'); var EditSession = require("./edit_session").EditSession; var Editor = require("./editor").Editor; var MockRenderer = require("./test/mockrenderer").MockRenderer; var Range = require("./range").Range; +var MultiSelect = require("./multi_select").MultiSelect; var assert = require("./test/assertions"); var IncrementalSearch = require("./incremental_search").IncrementalSearch; @@ -69,6 +71,7 @@ module.exports = { setUp: function() { var session = new EditSession(["abc123", "xyz124"]); editor = new Editor(new MockRenderer(), session); + new MultiSelect(editor); iSearch = new IncrementalSearch(); }, @@ -195,7 +198,7 @@ module.exports = { editor.keyBinding.addKeyboardHandler(emacs.handler); emacs.handler.commands.setMark.exec(editor); iSearch.activate(editor); - iSearch.addString('1'); iSearch.addString('2');; + iSearch.addString('1'); iSearch.addString('2'); testRanges("Range: [0/0] -> [0/5]", [editor.getSelectionRange()], "sel range"); } From 939e0739a7eded9bb9cf92ea697c54d00e799138 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Wed, 5 Nov 2014 11:50:14 -0800 Subject: [PATCH 09/11] adding improvements as suggested by @nightwing --- lib/ace/incremental_search_test.js | 4 ++-- lib/ace/keyboard/emacs.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ace/incremental_search_test.js b/lib/ace/incremental_search_test.js index f47e6b4f..1d359a9a 100644 --- a/lib/ace/incremental_search_test.js +++ b/lib/ace/incremental_search_test.js @@ -40,10 +40,11 @@ var EditSession = require("./edit_session").EditSession; var Editor = require("./editor").Editor; var MockRenderer = require("./test/mockrenderer").MockRenderer; var Range = require("./range").Range; -var MultiSelect = require("./multi_select").MultiSelect; var assert = require("./test/assertions"); var IncrementalSearch = require("./incremental_search").IncrementalSearch; +require("./multi_select"); + var editor, iSearch; function testRanges(str, ranges) { ranges = ranges || editor.selection.getAllRanges(); @@ -71,7 +72,6 @@ module.exports = { setUp: function() { var session = new EditSession(["abc123", "xyz124"]); editor = new Editor(new MockRenderer(), session); - new MultiSelect(editor); iSearch = new IncrementalSearch(); }, diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 272071d1..674feaaa 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -444,7 +444,7 @@ exports.handler.addCommands({ // in multi select mode, ea selection is handled individually if (args && args.count) { - if (editor.inMultiSelectMode) editor.forEachSelection({exec: moveToMark}); + if (editor.inMultiSelectMode) editor.forEachSelection(moveToMark); else moveToMark(); return; } From e83252130c93057bf5ce4b01c9547dc362c1dde4 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Thu, 6 Nov 2014 23:27:54 -0800 Subject: [PATCH 10/11] emacs: improved multi selection handling for emacs mark --- build | 2 +- lib/ace/keyboard/emacs.js | 57 +++++++++++++---------- lib/ace/keyboard/emacs_test.js | 83 +++++++++++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 25 deletions(-) diff --git a/build b/build index 17c02716..fc9d2cae 160000 --- a/build +++ b/build @@ -1 +1 @@ -Subproject commit 17c02716b7f116c7920f8ef07c8c2b0e20d77ec0 +Subproject commit fc9d2cae9fe8e6e95e74c86a31d21caadd8f9f39 diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 674feaaa..2b6bd2c4 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -125,6 +125,24 @@ exports.handler.attach = function(editor) { return this.session.$emacsMark || this.session.$emacsMarkRing.slice(-1)[0]; }; + editor.emacsMarkForSelection = function(replacement) { + // find the mark in $emacsMarkRing corresponding to the current + // selection + var sel = this.selection, + multiRangeLength = this.multiSelect ? + this.multiSelect.getAllRanges().length : 1, + selIndex = sel.index || 0, + markRing = this.session.$emacsMarkRing, + markIndex = markRing.length - (multiRangeLength - selIndex), + lastMark = markRing[markIndex] || sel.anchor; + if (replacement) { + markRing.splice(markIndex, 1, + "row" in replacement && "column" in replacement ? + replacement : undefined); + } + return lastMark; + } + editor.on("click", $resetMarkMode); editor.on("changeSession", $kbSessionChange); editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; @@ -446,6 +464,7 @@ exports.handler.addCommands({ if (args && args.count) { if (editor.inMultiSelectMode) editor.forEachSelection(moveToMark); else moveToMark(); + moveToMark(); return; } @@ -465,7 +484,7 @@ exports.handler.addCommands({ } if (!mark) { - rangePositions.slice(0,-1).forEach(function(pos) { editor.pushEmacsMark(pos); }); + rangePositions.forEach(function(pos) { editor.pushEmacsMark(pos); }); editor.setEmacsMark(rangePositions[rangePositions.length-1]); return; } @@ -479,30 +498,22 @@ exports.handler.addCommands({ }, readOnly: true, - handlesCount: true, - multiSelectAction: "forEach" + handlesCount: true }, exchangePointAndMark: { - exec: function (editor, args) { - var restoreMarks = []; - if (editor.inMultiSelectMode) editor.forEachSelection({exec: doExchange}); - else doExchange(); - restoreMarks.reverse().forEach(function(p) { editor.pushEmacsMark(p); }); - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - function doExchange() { - var sel = editor.selection; - if (args.count) { // replace mark and point - var pos = {row: sel.lead.row, column: sel.lead.column}; - restoreMarks.push(pos); - sel.clearSelection(); - sel.moveCursorToPosition(editor.popEmacsMark()); - } else if (sel.isEmpty()) { // move to mark, forget point - var lastMark = editor.popEmacsMark(); - restoreMarks.push(lastMark); - sel.selectToPosition(lastMark); - } else { // just invert selection - sel.setSelectionRange(sel.getRange(), !sel.isBackwards()); - } + exec: function exchangePointAndMark$exec(editor, args) { + var sel = editor.selection; + if (!args.count && !sel.isEmpty()) { // just invert selection + sel.setSelectionRange(sel.getRange(), !sel.isBackwards()); + return; + } + + if (args.count) { // replace mark and point + var pos = {row: sel.lead.row, column: sel.lead.column}; + sel.clearSelection(); + sel.moveCursorToPosition(editor.emacsMarkForSelection(pos)); + } else { // create selection to last mark + sel.selectToPosition(editor.emacsMarkForSelection()); } }, readOnly: true, diff --git a/lib/ace/keyboard/emacs_test.js b/lib/ace/keyboard/emacs_test.js index d1aba564..09693156 100644 --- a/lib/ace/keyboard/emacs_test.js +++ b/lib/ace/keyboard/emacs_test.js @@ -35,17 +35,29 @@ if (typeof process !== "undefined") { define(function(require, exports, module) { "use strict"; +require("../multi_select"); + var EditSession = require("./../edit_session").EditSession, Editor = require("./../editor").Editor, + Range = require("./../range").Range, MockRenderer = require("./../test/mockrenderer").MockRenderer, emacs = require('./emacs'), assert = require("./../test/assertions"), - editor; + editor, sel; function initEditor(docString) { var doc = new EditSession(docString.split("\n")); editor = new Editor(new MockRenderer(), doc); editor.setKeyboardHandler(emacs.handler); + sel = editor.selection; +} + +function print(obj) { + return JSON.stringify(obj, null, 2); +} + +function pluck(arr, what) { + return arr.map(function(ea) { return ea[what]; }); } module.exports = { @@ -62,6 +74,75 @@ module.exports = { editor.selectAll(); editor.execCommand('keyboardQuit'); assert.ok(editor.selection.isEmpty(), 'selection non-empty'); + }, + +// this.aceEditor.getSelectedText() +// this.aceEditor.selection.getAllRanges() +// lively.ide.ace.require("ace/range").Range.fromPoints(start, end) + "test: exchangePointAndMark without mark set": function() { + initEditor('foo'); + sel.setRange(Range.fromPoints({row: 0, column: 1}, {row: 0, column: 3})); + editor.execCommand('exchangePointAndMark'); + assert.deepEqual({row: 0, column: 1}, editor.getCursorPosition(), print(editor.getCursorPosition())); + }, + + "test: exchangePointAndMark with mark set": function() { + initEditor('foo'); + editor.pushEmacsMark({row: 0, column: 1}); + editor.pushEmacsMark({row: 0, column: 2}); + editor.execCommand('exchangePointAndMark', {count: 4}); + assert.deepEqual({row: 0, column: 2}, editor.getCursorPosition(), print(editor.getCursorPosition())); + assert.deepEqual([{row: 0, column: 1}, {row: 0, column: 0}], editor.session.$emacsMarkRing, print(editor.session.$emacsMarkRing)); + }, + + "test: exchangePointAndMark with selection": function() { + initEditor('foo'); + editor.pushEmacsMark({row: 0, column: 1}); + editor.pushEmacsMark({row: 0, column: 2}); + sel.setRange(Range.fromPoints({row: 0, column: 0}, {row: 0, column: 1}), true); + editor.execCommand('exchangePointAndMark'); + assert.deepEqual({row: 0, column: 1}, editor.getCursorPosition(), print(editor.getCursorPosition())); + assert.deepEqual([{row: 0, column: 1}, {row: 0, column: 2}], editor.session.$emacsMarkRing, print(editor.session.$emacsMarkRing)); + }, + + "test: exchangePointAndMark with multi selection": function() { + initEditor('foo\nhello world\n123'); + var ranges = [[{row: 0, column: 0}, {row: 0, column: 3}], + [{row: 1, column: 0}, {row: 1, column: 5}], + [{row: 1, column: 6}, {row: 1, column: 11}]] + ranges.forEach(function(r) { + sel.addRange(Range.fromPoints(r[0], r[1])); + }); + assert.equal("foo\nhello\nworld", editor.getSelectedText()); + editor.execCommand('exchangePointAndMark'); + assert.equal("foo\nhello\nworld", editor.getSelectedText()); + assert.deepEqual(pluck(ranges, 0), pluck(sel.getAllRanges(), 'cursor'), "selections dir not inverted"); + }, + + "test: exchangePointAndMark with multi cursors": function() { + initEditor('foo\nhello world\n123'); + var ranges = [[{row: 0, column: 0}, {row: 0, column: 3}], + [{row: 1, column: 0}, {row: 1, column: 5}], + [{row: 1, column: 6}, {row: 1, column: 11}]]; + // move cursors to the start of each range and set a mark to its end + // without selecting anything + ranges.forEach(function(r) { + editor.pushEmacsMark(r[1]); + sel.addRange(Range.fromPoints(r[0], r[0])); + }); + assert.deepEqual(pluck(ranges, 0), pluck(sel.getAllRanges(), 'cursor'), print(sel.getAllRanges())); + editor.execCommand('exchangePointAndMark'); + assert.deepEqual(pluck(ranges, 1), pluck(sel.getAllRanges(), 'cursor'), "not inverted: " + print(sel.getAllRanges())); + }, + + "test: setMark with multi cursors": function() { + initEditor('foo\nhello world\n123'); + var positions = [{row: 0, column: 0}, + {row: 1, column: 0}, + {row: 1, column: 6}]; + positions.forEach(function(p) { sel.addRange(Range.fromPoints(p,p)); }); + editor.execCommand('setMark'); + assert.deepEqual(positions, editor.session.$emacsMarkRing, print(editor.session.$emacsMarkRing)); } }; From d09740f4947ef64c37bb2a36af34c786ff552726 Mon Sep 17 00:00:00 2001 From: nightwing Date: Sat, 8 Nov 2014 15:57:23 +0400 Subject: [PATCH 11/11] fix emacs universal argument --- lib/ace/ext/statusbar.js | 5 ++--- lib/ace/keyboard/emacs.js | 31 +++++++++++++++++++------------ lib/ace/keyboard/emacs_test.js | 3 --- lib/ace/keyboard/keybinding.js | 10 +++++++++- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/lib/ace/ext/statusbar.js b/lib/ace/ext/statusbar.js index 666febfa..5e5b0572 100644 --- a/lib/ace/ext/statusbar.js +++ b/lib/ace/ext/statusbar.js @@ -28,9 +28,8 @@ var StatusBar = function(editor, parentNode) { str && status.push(str, separator || "|"); } - if (editor.$vimModeHandler) - add(editor.$vimModeHandler.getStatusText()); - else if (editor.commands.recording) + add(editor.keyBinding.getStatusText(editor)); + if (editor.commands.recording) add("REC"); var c = editor.selection.lead; diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 2b6bd2c4..945eddf3 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -164,6 +164,7 @@ exports.handler.detach = function(editor) { editor.commands.removeCommands(commands); editor.removeEventListener('copy', this.onCopy); editor.removeEventListener('paste', this.onPaste); + editor.$emacsModeHandler = null; }; var $kbSessionChange = function(e) { @@ -205,7 +206,7 @@ exports.handler.onCopy = function(e, editor) { if (editor.$handlesEmacsOnCopy) return; editor.$handlesEmacsOnCopy = true; exports.handler.commands.killRingSave.exec(editor); - delete editor.$handlesEmacsOnCopy; + editor.$handlesEmacsOnCopy = false; }; exports.handler.onPaste = function(e, editor) { @@ -238,12 +239,22 @@ exports.handler.bindKey = function(key, command) { }, this); }; +exports.handler.getStatusText = function(editor, data) { + var str = ""; + if (data.count) + str += data.count; + if (data.keyChain) + str += " " + data.keyChain + return str; +}; + exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { // if keyCode == -1 a non-printable key was pressed, such as just // control. Handling those is currently not supported in this handler if (keyCode === -1) return undefined; var editor = data.editor; + editor._signal("changeStatus"); // insertstring data.count times if (hashId == -1) { editor.pushEmacsMark(); @@ -254,24 +265,17 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { } } - if (key == "\x00") return undefined; - var modifier = eMods[hashId]; // CTRL + number / universalArgument for setting data.count - if (modifier == "c-" || data.universalArgument) { - var prevCount = String(data.count || 0); + if (modifier == "c-" || data.count) { var count = parseInt(key[key.length - 1]); if (typeof count === 'number' && !isNaN(count)) { - data.count = parseInt(prevCount + count); + data.count = Math.max(data.count, 0) || 0; + data.count = 10 * data.count + count; return {command: "null"}; - } else if (data.universalArgument) { - // if no number pressed use emacs defaults for universalArgument - // which is 4 - data.count = 4; } } - data.universalArgument = false; // this.commandKeyBinding maps key specs like "c-p" (for CTRL + P) to // command objects, for lookup key needs to include the modifier @@ -293,7 +297,9 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { if (command === "null") return {command: "null"}; if (command === "universalArgument") { - data.universalArgument = true; + // if no number pressed emacs repeats action 4 times. + // minus sign is needed to allow next keypress to replace it + data.count = -4; return {command: "null"}; } @@ -607,6 +613,7 @@ exports.handler.addCommands({ keyboardQuit: function(editor) { editor.selection.clearSelection(); editor.setEmacsMark(null); + editor.keyBinding.$data.count = null; }, focusCommandLine: function(editor, arg) { if (editor.showCommandLine) diff --git a/lib/ace/keyboard/emacs_test.js b/lib/ace/keyboard/emacs_test.js index 09693156..3ba5efaa 100644 --- a/lib/ace/keyboard/emacs_test.js +++ b/lib/ace/keyboard/emacs_test.js @@ -76,9 +76,6 @@ module.exports = { assert.ok(editor.selection.isEmpty(), 'selection non-empty'); }, -// this.aceEditor.getSelectedText() -// this.aceEditor.selection.getAllRanges() -// lively.ide.ace.require("ace/range").Range.fromPoints(start, end) "test: exchangePointAndMark without mark set": function() { initEditor('foo'); sel.setRange(Range.fromPoints({row: 0, column: 1}, {row: 0, column: 3})); diff --git a/lib/ace/keyboard/keybinding.js b/lib/ace/keyboard/keybinding.js index c43d7930..cddf0666 100644 --- a/lib/ace/keyboard/keybinding.js +++ b/lib/ace/keyboard/keybinding.js @@ -89,8 +89,16 @@ var KeyBinding = function(editor) { this.getKeyboardHandler = function() { return this.$handlers[this.$handlers.length - 1]; }; + + this.getStatusText = function() { + var data = this.$data; + var editor = data.editor; + return this.$handlers.map(function(h) { + return h.getStatusText && h.getStatusText(editor, data) || ""; + }).filter(Boolean).join(" "); + }; - this.$callKeyboardHandlers = function (hashId, keyString, keyCode, e) { + this.$callKeyboardHandlers = function(hashId, keyString, keyCode, e) { var toExecute; var success = false; var commands = this.$editor.commands;