Merge remote-tracking branch 'remotes/origin/emacs'
Conflicts: lib/ace/incremental_search_test.js
This commit is contained in:
commit
5562566b37
6 changed files with 188 additions and 58 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ function objectToRegExp(obj) {
|
|||
!!this.$editor.emacsMark() : !this.$editor.selection.isEmpty();
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ 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();
|
||||
|
|
@ -199,7 +201,6 @@ module.exports = {
|
|||
emacs.handler.commands.setMark.exec(editor);
|
||||
iSearch.activate(editor);
|
||||
iSearch.addString('1'); iSearch.addString('2');
|
||||
|
||||
testRanges("Range: [0/0] -> [0/5]", [editor.getSelectionRange()], "sel range");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -146,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) {
|
||||
|
|
@ -187,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) {
|
||||
|
|
@ -220,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();
|
||||
|
|
@ -236,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
|
||||
|
|
@ -275,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"};
|
||||
}
|
||||
|
||||
|
|
@ -318,7 +342,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,59 +464,63 @@ 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);
|
||||
if (editor.inMultiSelectMode) editor.forEachSelection(moveToMark);
|
||||
else moveToMark();
|
||||
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.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);
|
||||
|
||||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
function moveToMark() {
|
||||
var mark = editor.popEmacsMark();
|
||||
mark && editor.moveCursorToPosition(mark);
|
||||
}
|
||||
|
||||
},
|
||||
readOnly: true,
|
||||
handlesCount: true,
|
||||
multiSelectAction: "forEach"
|
||||
handlesCount: true
|
||||
},
|
||||
exchangePointAndMark: {
|
||||
exec: function(editor, args) {
|
||||
exec: function exchangePointAndMark$exec(editor, args) {
|
||||
var sel = editor.selection;
|
||||
if (args.count) {
|
||||
var pos = editor.getCursorPosition();
|
||||
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.popEmacsMark());
|
||||
editor.pushEmacsMark(pos);
|
||||
return;
|
||||
sel.moveCursorToPosition(editor.emacsMarkForSelection(pos));
|
||||
} else { // create selection to last mark
|
||||
sel.selectToPosition(editor.emacsMarkForSelection());
|
||||
}
|
||||
var lastMark = editor.getLastEmacsMark();
|
||||
var range = sel.getRange();
|
||||
if (range.isEmpty()) {
|
||||
sel.selectToPosition(lastMark);
|
||||
return;
|
||||
}
|
||||
sel.setSelectionRange(range, !sel.isBackwards());
|
||||
},
|
||||
readOnly: true,
|
||||
handlesCount: true,
|
||||
|
|
@ -544,6 +573,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 +587,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
|
||||
|
|
@ -570,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)
|
||||
|
|
|
|||
|
|
@ -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,72 @@ module.exports = {
|
|||
editor.selectAll();
|
||||
editor.execCommand('keyboardQuit');
|
||||
assert.ok(editor.selection.isEmpty(), 'selection non-empty');
|
||||
},
|
||||
|
||||
"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));
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue