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)); } };