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;
// todo: find a way to automatically exit multiselect mode
this.exitMultiSelectMode && this.exitMultiSelectMode()
this.exitMultiSelectMode && this.exitMultiSelectMode();
this.moveCursorTo(lineNumber - 1, column || 0);
this.$blockScrolling -= 1;

View file

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

View file

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

View file

@ -514,14 +514,14 @@ var Editor = require("./editor").Editor;
* @method Editor.exitMultiSelectMode
**/
this.exitMultiSelectMode = function() {
if (this.inVirtualSelectionMode)
if (!this.inMultiSelectMode || this.inVirtualSelectionMode)
return;
this.multiSelect.toSingleRange();
};
this.getCopyText = function() {
var text = "";
if (this.inMultiSelectMode) {
if (this.inMultiSelectMode && !this.inVirtualSelectionMode) {
var ranges = this.multiSelect.rangeList.ranges;
var buf = [];
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 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);
for (var i = ranges.length; i--; ) {
for (var i = ranges.length; i--;) {
var range = ranges[i];
if (!range.isEmpty())
this.session.remove(range);