diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 2952b80a..3ab016d0 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -50,9 +50,24 @@ var HashHandler = require("./hash_handler").HashHandler; exports.handler = new HashHandler(); var initialized = false; + +// When mark is set, keyboard cursor movement commands become selection +// modification commands. This is a little different than emacs. In +// emacs, keyboard cursor movement always sets mark, but it does not +// highlight the region unless mark has been otherwise explicitly set +// and transient-mark-mode is on. +// In ACE, there is no concept of a region that is not highlighted, +// so we just work with highlighted area === region. It would probably be +// confusing to most users anyway if a cut command, say, were to delete an +// area that was not highlighted. +var markMode; + exports.handler.attach = function(editor) { if (!initialized) { initialized = true; + + // in emacs, gotowordleft/right should not count a space as a word.. + editor.session.$selectLongWords = true; dom.importCssString('\ .emacs-mode .ace_cursor{\ border: 2px rgba(50,250,50,0.8) solid!important;\ @@ -79,6 +94,9 @@ exports.handler.attach = function(editor) { }', 'emacsMode' ); } + markMode = false; + + editor.on("click",$resetMarkMode); editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; editor.setStyle("emacs-mode"); @@ -87,8 +105,12 @@ exports.handler.attach = function(editor) { exports.handler.detach = function(editor) { delete editor.renderer.screenToTextCoordinates; editor.unsetStyle("emacs-mode"); + editor.removeEventListener("click",$resetMarkMode); }; +var $resetMarkMode = function(e) { + markMode = null; +} var keys = require("../lib/keys").KEY_MODS; var eMods = { @@ -119,6 +141,7 @@ exports.handler.bindKey = function(key, command) { exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { if (hashId == -1) { + markMode = null; if (data.count) { var str = Array(data.count + 1).join(key); data.count = null; @@ -162,9 +185,21 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { if (typeof command != "string") { var args = command.args; command = command.command; + if (command == "goorselect") { + command = args[0]; + if (markMode) { + command = args[1]; + } + args = null; + } } if (typeof command == "string") { + if (command == "insertstring" || + command == "splitline" || + command == "togglecomment") { + markMode = null; + } command = this.commands[command] || data.editor.commands.commands[command]; } @@ -190,16 +225,16 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { exports.emacsKeys = { // movement - "Up|C-p" : "golineup", - "Down|C-n" : "golinedown", - "Left|C-b" : "gotoleft", - "Right|C-f" : "gotoright", - "C-Left|M-b" : "gotowordleft", - "C-Right|M-f" : "gotowordright", - "Home|C-a" : "gotolinestart", - "End|C-e" : "gotolineend", - "C-Home|S-M-,": "gotostart", - "C-End|S-M-." : "gotoend", + "Up|C-p" : {command: "goorselect", args: ["golineup","selectup"]}, + "Down|C-n" : {command: "goorselect", args: ["golinedown","selectdown"]}, + "Left|C-b" : {command: "goorselect", args: ["gotoleft","selectleft"]}, + "Right|C-f" : {command: "goorselect", args: ["gotoright","selectright"]}, + "C-Left|M-b" : {command: "goorselect", args: ["gotowordleft","selectwordleft"]}, + "C-Right|M-f" : {command: "goorselect", args: ["gotowordright","selectwordright"]}, + "Home|C-a" : {command: "goorselect", args: ["gotolinestart","selecttolinestart"]}, + "End|C-e" : {command: "goorselect", args: ["gotolineend","selecttolineend"]}, + "C-Home|S-M-,": {command: "goorselect", args: ["gotostart","selecttostart"]}, + "C-End|S-M-." : {command: "goorselect", args: ["gotoend","selecttoend"]}, // selection "S-Up|S-C-p" : "selectup", @@ -219,10 +254,10 @@ exports.emacsKeys = { "C-x C-p": "selectall", // todo fix these - "C-Down": "gotopagedown", - "C-Up": "gotopageup", - "PageDown|C-v": "gotopagedown", - "PageUp|M-v": "gotopageup", + "C-Down": {command: "goorselect", args: ["gotopagedown","selectpagedown"]}, + "C-Up": {command: "goorselect", args: ["gotopageup","selectpageup"]}, + "PageDown|C-v": {command: "goorselect", args: ["gotopagedown","selectpagedown"]}, + "PageUp|M-v": {command: "goorselect", args: ["gotopageup","selectpageup"]}, "S-C-Down": "selectpagedown", "S-C-Up": "selectpageup", "C-s": "findnext", @@ -247,16 +282,15 @@ exports.emacsKeys = { "C-w": "killRegion", "M-w": "killRingSave", - "C-Space": "setMark", "C-x C-x": "exchangePointAndMark", "C-t": "transposeletters", - - "M-u": "touppercase", + "M-u": "touppercase", // Doesn't work "M-l": "tolowercase", - "M-/": "autocomplete", - "C-u": "universalArgument", + "M-/": "autocomplete", // Doesn't work + "C-u": "universalArgument", + "M-;": "togglecomment", "C-/|C-x u|S-C--|C-z": "undo", @@ -290,10 +324,32 @@ exports.handler.addCommands({ editor.multiSelect.toggleBlockSelection(); }, setMark: function() { + // Sets mark-mode and clears current selection. + // When in mark-mode, "goto" commands become "select" commands. + // Any insertion or mouse click resets mark-mode. + + if (markMode) { + + cp = editor.getCursorPosition(); + if (editor.selection.isEmpty() && + markMode.row == cp.row && markMode.column == cp.column) { + // setMark twice in a row at the same place + // resets markmode + markMode = null; + console.log("Mark mode off"); + return; + } + } + + // turn on mark mode + markMode = editor.getCursorPosition(); + editor.selection.setSelectionAnchor(markMode.row, markMode.column); + }, exchangePointAndMark: { exec: function(editor) { var range = editor.selection.getRange(); + editor.selection.setSelectionRange(range, !editor.selection.isBackwards()); }, readonly: true, @@ -317,7 +373,21 @@ exports.handler.addCommands({ multiselectAction: "forEach" }, killLine: function(editor) { - editor.selection.selectLine(); + markMode = null; + + if (editor.getCursorPosition().column == 0 && + editor.selection.isEmpty) { + // If an already empty line is killed, remove + // the line entirely + editor.selection.selectLine(); + } else { + // otherwise just remove from the current cursor position + // to the end (but don't delete the selection if it's before + // the cursor) + editor.clearSelection(); + editor.selection.selectLineEnd(); + + } var range = editor.getSelectionRange(); var text = editor.session.getTextRange(range); exports.killRing.add(text);