emacs: improved multi selection handling for emacs mark
This commit is contained in:
parent
939e0739a7
commit
e83252130c
3 changed files with 117 additions and 25 deletions
2
build
2
build
|
|
@ -1 +1 @@
|
||||||
Subproject commit 17c02716b7f116c7920f8ef07c8c2b0e20d77ec0
|
Subproject commit fc9d2cae9fe8e6e95e74c86a31d21caadd8f9f39
|
||||||
|
|
@ -125,6 +125,24 @@ exports.handler.attach = function(editor) {
|
||||||
return this.session.$emacsMark || this.session.$emacsMarkRing.slice(-1)[0];
|
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("click", $resetMarkMode);
|
||||||
editor.on("changeSession", $kbSessionChange);
|
editor.on("changeSession", $kbSessionChange);
|
||||||
editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates;
|
editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates;
|
||||||
|
|
@ -446,6 +464,7 @@ exports.handler.addCommands({
|
||||||
if (args && args.count) {
|
if (args && args.count) {
|
||||||
if (editor.inMultiSelectMode) editor.forEachSelection(moveToMark);
|
if (editor.inMultiSelectMode) editor.forEachSelection(moveToMark);
|
||||||
else moveToMark();
|
else moveToMark();
|
||||||
|
moveToMark();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -465,7 +484,7 @@ exports.handler.addCommands({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mark) {
|
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]);
|
editor.setEmacsMark(rangePositions[rangePositions.length-1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -479,30 +498,22 @@ exports.handler.addCommands({
|
||||||
|
|
||||||
},
|
},
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
handlesCount: true,
|
handlesCount: true
|
||||||
multiSelectAction: "forEach"
|
|
||||||
},
|
},
|
||||||
exchangePointAndMark: {
|
exchangePointAndMark: {
|
||||||
exec: function (editor, args) {
|
exec: function exchangePointAndMark$exec(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;
|
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
|
if (args.count) { // replace mark and point
|
||||||
var pos = {row: sel.lead.row, column: sel.lead.column};
|
var pos = {row: sel.lead.row, column: sel.lead.column};
|
||||||
restoreMarks.push(pos);
|
|
||||||
sel.clearSelection();
|
sel.clearSelection();
|
||||||
sel.moveCursorToPosition(editor.popEmacsMark());
|
sel.moveCursorToPosition(editor.emacsMarkForSelection(pos));
|
||||||
} else if (sel.isEmpty()) { // move to mark, forget point
|
} else { // create selection to last mark
|
||||||
var lastMark = editor.popEmacsMark();
|
sel.selectToPosition(editor.emacsMarkForSelection());
|
||||||
restoreMarks.push(lastMark);
|
|
||||||
sel.selectToPosition(lastMark);
|
|
||||||
} else { // just invert selection
|
|
||||||
sel.setSelectionRange(sel.getRange(), !sel.isBackwards());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
|
|
|
||||||
|
|
@ -35,17 +35,29 @@ if (typeof process !== "undefined") {
|
||||||
define(function(require, exports, module) {
|
define(function(require, exports, module) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
require("../multi_select");
|
||||||
|
|
||||||
var EditSession = require("./../edit_session").EditSession,
|
var EditSession = require("./../edit_session").EditSession,
|
||||||
Editor = require("./../editor").Editor,
|
Editor = require("./../editor").Editor,
|
||||||
|
Range = require("./../range").Range,
|
||||||
MockRenderer = require("./../test/mockrenderer").MockRenderer,
|
MockRenderer = require("./../test/mockrenderer").MockRenderer,
|
||||||
emacs = require('./emacs'),
|
emacs = require('./emacs'),
|
||||||
assert = require("./../test/assertions"),
|
assert = require("./../test/assertions"),
|
||||||
editor;
|
editor, sel;
|
||||||
|
|
||||||
function initEditor(docString) {
|
function initEditor(docString) {
|
||||||
var doc = new EditSession(docString.split("\n"));
|
var doc = new EditSession(docString.split("\n"));
|
||||||
editor = new Editor(new MockRenderer(), doc);
|
editor = new Editor(new MockRenderer(), doc);
|
||||||
editor.setKeyboardHandler(emacs.handler);
|
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 = {
|
module.exports = {
|
||||||
|
|
@ -62,6 +74,75 @@ module.exports = {
|
||||||
editor.selectAll();
|
editor.selectAll();
|
||||||
editor.execCommand('keyboardQuit');
|
editor.execCommand('keyboardQuit');
|
||||||
assert.ok(editor.selection.isEmpty(), 'selection non-empty');
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue