Merge pull request #1446 from LivelyKernel/emacs-fixes

Emacs fixes
This commit is contained in:
Harutyun Amirjanyan 2013-05-27 13:15:55 -07:00
commit 0e1d74622b
4 changed files with 169 additions and 63 deletions

View file

@ -1800,7 +1800,7 @@ var Editor = function(renderer, session) {
this.$blockScrolling += 1; this.$blockScrolling += 1;
// todo: find a way to automatically exit multiselect mode // todo: find a way to automatically exit multiselect mode
this.exitMultiSelectMode && this.exitMultiSelectMode() this.exitMultiSelectMode && this.exitMultiSelectMode();
this.moveCursorTo(lineNumber - 1, column || 0); this.moveCursorTo(lineNumber - 1, column || 0);
this.$blockScrolling -= 1; this.$blockScrolling -= 1;

View file

@ -113,6 +113,8 @@ oop.inherits(IncrementalSearch, Search);
if (reset) { if (reset) {
e.moveCursorToPosition(this.$startPos); e.moveCursorToPosition(this.$startPos);
this.$currentPos = this.$startPos; this.$currentPos = this.$startPos;
} else {
e.pushEmacsMark && e.pushEmacsMark(this.$startPos, false);
} }
this.highlight(null); this.highlight(null);
return Range.fromPoints(this.$currentPos, this.$currentPos); return Range.fromPoints(this.$currentPos, this.$currentPos);

View file

@ -95,16 +95,36 @@ exports.handler.attach = function(editor) {
$formerLineStart = editor.session.$useEmacsStyleLineStart; $formerLineStart = editor.session.$useEmacsStyleLineStart;
editor.session.$useEmacsStyleLineStart = true; editor.session.$useEmacsStyleLineStart = true;
editor.session.$emacsMark = null; editor.session.$emacsMark = null; // the active mark
editor.session.$emacsMarkRing = editor.session.$emacsMarkRing || [];
editor.emacsMarkMode = function() { editor.emacsMark = function() {
return this.session.$emacsMark; return this.session.$emacsMark;
} }
editor.setEmacsMarkMode = function(p) { editor.setEmacsMark = function(p) {
// to deactivate pass in a falsy value
this.session.$emacsMark = p; this.session.$emacsMark = p;
} }
editor.pushEmacsMark = function(p, activate) {
var prevMark = this.session.$emacsMark;
if (prevMark)
this.session.$emacsMarkRing.push(prevMark);
if (!p || activate) this.setEmacsMark(p)
else this.session.$emacsMarkRing.push(p);
}
editor.popEmacsMark = function() {
var mark = this.emacsMark();
if (mark) { this.setEmacsMark(null); return mark; }
return this.session.$emacsMarkRing.pop();
}
editor.getLastEmacsMark = function(p) {
return this.session.$emacsMark || this.session.$emacsMarkRing.slice(-1)[0];
}
editor.on("click", $resetMarkMode); editor.on("click", $resetMarkMode);
editor.on("changeSession", $kbSessionChange); editor.on("changeSession", $kbSessionChange);
editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates;
@ -112,6 +132,8 @@ exports.handler.attach = function(editor) {
editor.commands.addCommands(commands); editor.commands.addCommands(commands);
exports.handler.platform = editor.commands.platform; exports.handler.platform = editor.commands.platform;
editor.$emacsModeHandler = this; editor.$emacsModeHandler = this;
editor.addEventListener('copy', this.onCopy);
editor.addEventListener('paste', this.onPaste);
}; };
exports.handler.detach = function(editor) { exports.handler.detach = function(editor) {
@ -122,6 +144,8 @@ exports.handler.detach = function(editor) {
editor.removeEventListener("changeSession", $kbSessionChange); editor.removeEventListener("changeSession", $kbSessionChange);
editor.unsetStyle("emacs-mode"); editor.unsetStyle("emacs-mode");
editor.commands.removeCommands(commands); editor.commands.removeCommands(commands);
editor.removeEventListener('copy', this.onCopy);
editor.removeEventListener('paste', this.onPaste);
}; };
var $kbSessionChange = function(e) { var $kbSessionChange = function(e) {
@ -137,6 +161,8 @@ var $kbSessionChange = function(e) {
if (!e.session.hasOwnProperty('$emacsMark')) if (!e.session.hasOwnProperty('$emacsMark'))
e.session.$emacsMark = null; e.session.$emacsMark = null;
if (!e.session.hasOwnProperty('$emacsMarkRing'))
e.session.$emacsMarkRing = [];
} }
var $resetMarkMode = function(e) { var $resetMarkMode = function(e) {
@ -157,6 +183,17 @@ combinations.forEach(function(c) {
eMods[hashId] = c.toLowerCase() + "-"; eMods[hashId] = c.toLowerCase() + "-";
}); });
exports.handler.onCopy = function(e, editor) {
if (editor.$handlesEmacsOnCopy) return;
editor.$handlesEmacsOnCopy = true;
exports.handler.commands.killRingSave.exec(editor);
delete editor.$handlesEmacsOnCopy;
}
exports.handler.onPaste = function(e, editor) {
editor.pushEmacsMark(editor.getCursorPosition());
}
exports.handler.bindKey = function(key, command) { exports.handler.bindKey = function(key, command) {
if (!key) if (!key)
return; return;
@ -165,18 +202,27 @@ exports.handler.bindKey = function(key, command) {
key.split("|").forEach(function(keyPart) { key.split("|").forEach(function(keyPart) {
keyPart = keyPart.toLowerCase(); keyPart = keyPart.toLowerCase();
ckb[keyPart] = command; ckb[keyPart] = command;
keyPart = keyPart.split(" ")[0]; // register all partial key combos as null commands
if (!ckb[keyPart]) // to be able to activate key combos with arbitrary length
ckb[keyPart] = "null"; // Example: if keyPart is "C-c C-l t" then "C-c C-l t" will
// get command assigned and "C-c" and "C-c C-l" will get
// a null command assigned in this.commmandKeyBinding. For
// the lookup logic see handleKeyboard()
var keyParts = keyPart.split(" ").slice(0,-1);
keyParts.reduce(function(keyMapKeys, keyPart, i) {
var prefix = keyMapKeys[i-1] ? keyMapKeys[i-1] + ' ' : '';
return keyMapKeys.concat([prefix + keyPart]);
}, []).forEach(function(keyPart) {
if (!ckb[keyPart]) ckb[keyPart] = "null";
});
}, this); }, this);
}; }
exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
var editor = data.editor; var editor = data.editor;
// insertstring data.count times // insertstring data.count times
if (hashId == -1) { if (hashId == -1) {
editor.setEmacsMarkMode(null); editor.pushEmacsMark();
if (data.count) { if (data.count) {
var str = Array(data.count + 1).join(key); var str = Array(data.count + 1).join(key);
data.count = null; data.count = null;
@ -190,10 +236,15 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
// CTRL + number / universalArgument for setting data.count // CTRL + number / universalArgument for setting data.count
if (modifier == "c-" || data.universalArgument) { if (modifier == "c-" || data.universalArgument) {
var prevCount = String(data.count || 0);
var count = parseInt(key[key.length - 1]); var count = parseInt(key[key.length - 1]);
if (count) { if (typeof count === 'number' && !isNaN(count)) {
data.count = count; data.count = parseInt(prevCount + 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; data.universalArgument = false;
@ -230,7 +281,7 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
args = command.args; args = command.args;
if (command.command) command = command.command; if (command.command) command = command.command;
if (command === "goorselect") { if (command === "goorselect") {
command = editor.emacsMarkMode() ? args[1] : args[0]; command = editor.emacsMark() ? args[1] : args[0];
args = null; args = null;
} }
} }
@ -239,7 +290,7 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
if (command === "insertstring" || if (command === "insertstring" ||
command === "splitline" || command === "splitline" ||
command === "togglecomment") { command === "togglecomment") {
editor.setEmacsMarkMode(null); editor.pushEmacsMark();
} }
command = this.commands[command] || editor.commands.commands[command]; command = this.commands[command] || editor.commands.commands[command];
if (!command) return undefined; if (!command) return undefined;
@ -251,15 +302,20 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
if (data.count) { if (data.count) {
var count = data.count; var count = data.count;
data.count = 0; data.count = 0;
return { if (!command || !command.handlesCount) {
args: args, return {
command: { args: args,
exec: function(editor, args) { command: {
for (var i = 0; i < count; i++) exec: function(editor, args) {
command.exec(editor, args); for (var i = 0; i < count; i++)
command.exec(editor, args);
}
} }
} };
}; } else {
if (!args) args = {}
if (typeof args === 'object') args.count = count;
}
} }
return {command: command, args: args}; return {command: command, args: args};
@ -367,36 +423,70 @@ exports.handler.addCommands({
selectRectangularRegion: function(editor) { selectRectangularRegion: function(editor) {
editor.multiSelect.toggleBlockSelection(); editor.multiSelect.toggleBlockSelection();
}, },
setMark: function(editor) { setMark: {
// Emulate emacs highlighting behaviour in transient-mark-mode. exec: function(editor, args) {
// Sets mark-mode and clears current selection. // Sets mark-mode and clears current selection.
// When mark is set, keyboard cursor movement commands become // When mark is set, keyboard cursor movement commands become
// 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
var markMode = editor.emacsMarkMode(); if (args && args.count) {
if (markMode) { var mark = editor.popEmacsMark();
var cp = editor.getCursorPosition(); mark && editor.selection.moveCursorToPosition(mark);
if (editor.selection.isEmpty() &&
markMode.row == cp.row && markMode.column == cp.column) {
editor.setEmacsMarkMode(null);
// console.log("Mark mode off");
return; return;
} }
}
// turn on mark mode var mark = editor.emacsMark(),
markMode = editor.getCursorPosition(); transientMarkModeActive = true;
editor.setEmacsMarkMode(markMode);
editor.selection.setSelectionAnchor(markMode.row, markMode.column); // if transientMarkModeActive then mark behavior is a little
}, // different. Deactivate the mark when setMark is run with active
exchangePointAndMark: { // mark
exec: function(editor) { if (transientMarkModeActive && (mark || !editor.selection.isEmpty())) {
var range = editor.selection.getRange(); editor.pushEmacsMark();
editor.selection.setSelectionRange(range, !editor.selection.isBackwards()); editor.clearSelection();
return;
}
if (mark) {
var cp = editor.getCursorPosition();
if (editor.selection.isEmpty() &&
mark.row == cp.row && mark.column == cp.column) {
editor.pushEmacsMark();
return;
}
}
// turn on mark mode
mark = editor.getCursorPosition();
editor.setEmacsMark(mark);
editor.selection.setSelectionAnchor(mark.row, mark.column);
}, },
readonly: true, readonly: true,
multiselectAction: "forEach" handlesCount: true,
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());
},
readonly: true,
handlesCount: true,
multiSelectAction: "forEach"
}, },
killWord: { killWord: {
exec: function(editor, dir) { exec: function(editor, dir) {
@ -413,10 +503,10 @@ exports.handler.addCommands({
editor.session.remove(range); editor.session.remove(range);
editor.clearSelection(); editor.clearSelection();
}, },
multiselectAction: "forEach" multiSelectAction: "forEach"
}, },
killLine: function(editor) { killLine: function(editor) {
editor.setEmacsMarkMode(null); editor.pushEmacsMark(null);
var pos = editor.getCursorPosition(); var pos = editor.getCursorPosition();
if (pos.column == 0 && if (pos.column == 0 &&
editor.session.doc.getLine(pos.row).length == 0) { editor.session.doc.getLine(pos.row).length == 0) {
@ -438,7 +528,7 @@ exports.handler.addCommands({
editor.clearSelection(); editor.clearSelection();
}, },
yank: function(editor) { yank: function(editor) {
editor.onPaste(exports.killRing.get()); editor.onPaste(exports.killRing.get() || '');
editor.keyBinding.$data.lastCommand = "yank"; editor.keyBinding.$data.lastCommand = "yank";
}, },
yankRotate: function(editor) { yankRotate: function(editor) {
@ -448,16 +538,29 @@ exports.handler.addCommands({
editor.onPaste(exports.killRing.rotate()); editor.onPaste(exports.killRing.rotate());
editor.keyBinding.$data.lastCommand = "yank"; editor.keyBinding.$data.lastCommand = "yank";
}, },
killRegion: function(editor) { killRegion: {
exports.killRing.add(editor.getCopyText()); exec: function(editor) {
editor.commands.byName.cut.exec(editor); exports.killRing.add(editor.getCopyText());
editor.commands.byName.cut.exec(editor);
},
readonly: true,
multiSelectAction: "forEach"
}, },
killRingSave: function(editor) { killRingSave: {
exports.killRing.add(editor.getCopyText()); exec: function(editor) {
exports.killRing.add(editor.getCopyText());
setTimeout(function() {
var sel = editor.selection,
range = sel.getRange();
editor.pushEmacsMark(sel.isBackwards() ? range.end : range.start);
sel.clearSelection();
}, 0);
},
readonly: true
}, },
keyboardQuit: function(editor) { keyboardQuit: function(editor) {
editor.selection.clearSelection(); editor.selection.clearSelection();
editor.setEmacsMarkMode(null); editor.setEmacsMark(null);
}, },
focusCommandLine: function(editor, arg) { focusCommandLine: function(editor, arg) {
if (editor.showCommandLine) if (editor.showCommandLine)
@ -478,8 +581,9 @@ exports.killRing = {
if (this.$data.length > 30) if (this.$data.length > 30)
this.$data.shift(); this.$data.shift();
}, },
get: function() { get: function(n) {
return this.$data[this.$data.length - 1] || ""; n = n || 1;
return this.$data.slice(this.$data.length-n, this.$data.length).reverse().join('\n');
}, },
pop: function() { pop: function() {
if (this.$data.length > 1) if (this.$data.length > 1)

View file

@ -514,14 +514,14 @@ var Editor = require("./editor").Editor;
* @method Editor.exitMultiSelectMode * @method Editor.exitMultiSelectMode
**/ **/
this.exitMultiSelectMode = function() { this.exitMultiSelectMode = function() {
if (this.inVirtualSelectionMode) if (!this.inMultiSelectMode || this.inVirtualSelectionMode)
return; return;
this.multiSelect.toSingleRange(); this.multiSelect.toSingleRange();
}; };
this.getCopyText = function() { this.getCopyText = function() {
var text = ""; var text = "";
if (this.inMultiSelectMode) { if (this.inMultiSelectMode && !this.inVirtualSelectionMode) {
var ranges = this.multiSelect.rangeList.ranges; var ranges = this.multiSelect.rangeList.ranges;
var buf = []; var buf = [];
for (var i = 0; i < ranges.length; i++) { for (var i = 0; i < ranges.length; i++) {
@ -550,10 +550,10 @@ var Editor = require("./editor").Editor;
var lines = text.split(/\r\n|\r|\n/); var lines = text.split(/\r\n|\r|\n/);
var ranges = this.selection.rangeList.ranges; var ranges = this.selection.rangeList.ranges;
if (lines.length > ranges.length || (lines.length <= 2 && !lines[1])) if (lines.length > ranges.length || lines.length < 2 || !lines[1])
return this.commands.exec("insertstring", this, text); return this.commands.exec("insertstring", this, text);
for (var i = ranges.length; i--; ) { for (var i = ranges.length; i--;) {
var range = ranges[i]; var range = ranges[i];
if (!range.isEmpty()) if (!range.isEmpty())
this.session.remove(range); this.session.remove(range);