Merge remote-tracking branch 'remotes/origin/emacs'

Conflicts:
	lib/ace/incremental_search_test.js
This commit is contained in:
nightwing 2014-11-08 16:02:02 +04:00
commit 5562566b37
6 changed files with 188 additions and 58 deletions

View file

@ -28,9 +28,8 @@ var StatusBar = function(editor, parentNode) {
str && status.push(str, separator || "|"); str && status.push(str, separator || "|");
} }
if (editor.$vimModeHandler) add(editor.keyBinding.getStatusText(editor));
add(editor.$vimModeHandler.getStatusText()); if (editor.commands.recording)
else if (editor.commands.recording)
add("REC"); add("REC");
var c = editor.selection.lead; var c = editor.selection.lead;

View file

@ -173,7 +173,7 @@ function objectToRegExp(obj) {
!!this.$editor.emacsMark() : !this.$editor.selection.isEmpty(); !!this.$editor.emacsMark() : !this.$editor.selection.isEmpty();
if (found) { if (found) {
if (options.backwards) found = Range.fromPoints(found.end, found.start); 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; if (moveToNext) this.$currentPos = found.end;
// highlight after cursor move, so selection works properly // highlight after cursor move, so selection works properly
this.highlight(options.re) this.highlight(options.re)

View file

@ -44,6 +44,8 @@ var MultiSelect = require("./multi_select").MultiSelect;
var assert = require("./test/assertions"); var assert = require("./test/assertions");
var IncrementalSearch = require("./incremental_search").IncrementalSearch; var IncrementalSearch = require("./incremental_search").IncrementalSearch;
require("./multi_select");
var editor, iSearch; var editor, iSearch;
function testRanges(str, ranges) { function testRanges(str, ranges) {
ranges = ranges || editor.selection.getAllRanges(); ranges = ranges || editor.selection.getAllRanges();
@ -199,7 +201,6 @@ module.exports = {
emacs.handler.commands.setMark.exec(editor); emacs.handler.commands.setMark.exec(editor);
iSearch.activate(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"); testRanges("Range: [0/0] -> [0/5]", [editor.getSelectionRange()], "sel range");
} }

View file

@ -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;
@ -146,6 +164,7 @@ exports.handler.detach = function(editor) {
editor.commands.removeCommands(commands); editor.commands.removeCommands(commands);
editor.removeEventListener('copy', this.onCopy); editor.removeEventListener('copy', this.onCopy);
editor.removeEventListener('paste', this.onPaste); editor.removeEventListener('paste', this.onPaste);
editor.$emacsModeHandler = null;
}; };
var $kbSessionChange = function(e) { var $kbSessionChange = function(e) {
@ -187,7 +206,7 @@ exports.handler.onCopy = function(e, editor) {
if (editor.$handlesEmacsOnCopy) return; if (editor.$handlesEmacsOnCopy) return;
editor.$handlesEmacsOnCopy = true; editor.$handlesEmacsOnCopy = true;
exports.handler.commands.killRingSave.exec(editor); exports.handler.commands.killRingSave.exec(editor);
delete editor.$handlesEmacsOnCopy; editor.$handlesEmacsOnCopy = false;
}; };
exports.handler.onPaste = function(e, editor) { exports.handler.onPaste = function(e, editor) {
@ -220,12 +239,22 @@ exports.handler.bindKey = function(key, command) {
}, this); }, 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) { exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
// if keyCode == -1 a non-printable key was pressed, such as just // if keyCode == -1 a non-printable key was pressed, such as just
// control. Handling those is currently not supported in this handler // control. Handling those is currently not supported in this handler
if (keyCode === -1) return undefined; if (keyCode === -1) return undefined;
var editor = data.editor; var editor = data.editor;
editor._signal("changeStatus");
// insertstring data.count times // insertstring data.count times
if (hashId == -1) { if (hashId == -1) {
editor.pushEmacsMark(); editor.pushEmacsMark();
@ -236,24 +265,17 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
} }
} }
if (key == "\x00") return undefined;
var modifier = eMods[hashId]; var modifier = eMods[hashId];
// CTRL + number / universalArgument for setting data.count // CTRL + number / universalArgument for setting data.count
if (modifier == "c-" || data.universalArgument) { if (modifier == "c-" || data.count) {
var prevCount = String(data.count || 0);
var count = parseInt(key[key.length - 1]); var count = parseInt(key[key.length - 1]);
if (typeof count === 'number' && !isNaN(count)) { 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"}; 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 // this.commandKeyBinding maps key specs like "c-p" (for CTRL + P) to
// command objects, for lookup key needs to include the modifier // 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 === "null") return {command: "null"};
if (command === "universalArgument") { 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"}; return {command: "null"};
} }
@ -318,7 +342,8 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
exec: function(editor, args) { exec: function(editor, args) {
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
command.exec(editor, args); command.exec(editor, args);
} },
multiSelectAction: command.multiSelectAction
} }
}; };
} else { } else {
@ -439,59 +464,63 @@ exports.handler.addCommands({
// selection modification commands. That is, // selection modification commands. That is,
// "goto" commands become "select" commands. // "goto" commands become "select" commands.
// Any insertion or mouse click resets mark-mode. // 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) { if (args && args.count) {
var mark = editor.popEmacsMark(); if (editor.inMultiSelectMode) editor.forEachSelection(moveToMark);
mark && editor.selection.moveCursorToPosition(mark); else moveToMark();
moveToMark();
return; return;
} }
var mark = editor.emacsMark(), 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 // if transientMarkModeActive then mark behavior is a little
// different. Deactivate the mark when setMark is run with active // different. Deactivate the mark when setMark is run with active
// mark // mark
if (transientMarkModeActive && (mark || !editor.selection.isEmpty())) { if (transientMarkModeActive && (mark || !hasNoSelection)) {
editor.pushEmacsMark(); if (editor.inMultiSelectMode) editor.forEachSelection({exec: editor.clearSelection.bind(editor)})
editor.clearSelection(); else editor.clearSelection();
if (mark) editor.pushEmacsMark(null);
return; return;
} }
if (mark) { if (!mark) {
var cp = editor.getCursorPosition(); rangePositions.forEach(function(pos) { editor.pushEmacsMark(pos); });
if (editor.selection.isEmpty() && editor.setEmacsMark(rangePositions[rangePositions.length-1]);
mark.row == cp.row && mark.column == cp.column) { return;
editor.pushEmacsMark();
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, readOnly: true,
handlesCount: true, handlesCount: true
multiSelectAction: "forEach"
}, },
exchangePointAndMark: { exchangePointAndMark: {
exec: function(editor, args) { exec: function exchangePointAndMark$exec(editor, args) {
var sel = editor.selection; var sel = editor.selection;
if (args.count) { if (!args.count && !sel.isEmpty()) { // just invert selection
var pos = editor.getCursorPosition(); 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.clearSelection();
sel.moveCursorToPosition(editor.popEmacsMark()); sel.moveCursorToPosition(editor.emacsMarkForSelection(pos));
editor.pushEmacsMark(pos); } else { // create selection to last mark
return; 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, readOnly: true,
handlesCount: true, handlesCount: true,
@ -544,6 +573,7 @@ exports.handler.addCommands({
if (editor.keyBinding.$data.lastCommand != "yank") if (editor.keyBinding.$data.lastCommand != "yank")
return; return;
editor.undo(); editor.undo();
editor.session.$emacsMarkRing.pop(); // also undo recording mark
editor.onPaste(exports.killRing.rotate()); editor.onPaste(exports.killRing.rotate());
editor.keyBinding.$data.lastCommand = "yank"; editor.keyBinding.$data.lastCommand = "yank";
}, },
@ -557,12 +587,25 @@ exports.handler.addCommands({
}, },
killRingSave: { killRingSave: {
exec: function(editor) { 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()); exports.killRing.add(editor.getCopyText());
setTimeout(function() { setTimeout(function() {
var sel = editor.selection, function deselect() {
range = sel.getRange(); var sel = editor.selection, range = sel.getRange(),
editor.pushEmacsMark(sel.isBackwards() ? range.end : range.start); pos = sel.isBackwards() ? range.end : range.start;
sel.clearSelection(); 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); }, 0);
}, },
readOnly: true readOnly: true
@ -570,6 +613,7 @@ exports.handler.addCommands({
keyboardQuit: function(editor) { keyboardQuit: function(editor) {
editor.selection.clearSelection(); editor.selection.clearSelection();
editor.setEmacsMark(null); editor.setEmacsMark(null);
editor.keyBinding.$data.count = null;
}, },
focusCommandLine: function(editor, arg) { focusCommandLine: function(editor, arg) {
if (editor.showCommandLine) if (editor.showCommandLine)

View file

@ -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,72 @@ 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');
},
"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));
} }
}; };

View file

@ -89,8 +89,16 @@ var KeyBinding = function(editor) {
this.getKeyboardHandler = function() { this.getKeyboardHandler = function() {
return this.$handlers[this.$handlers.length - 1]; 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 toExecute;
var success = false; var success = false;
var commands = this.$editor.commands; var commands = this.$editor.commands;