From 984b4ea2e2d9976e30b0b4745bff73cc9f2c42ef Mon Sep 17 00:00:00 2001 From: Chester Wood Date: Fri, 25 Jan 2013 18:18:47 -0700 Subject: [PATCH 1/5] merge emacs fixes --- lib/ace/keyboard/emacs.js | 110 +++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 20 deletions(-) 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); From db34cf6a207ee9e654d811bb862a259f038d444d Mon Sep 17 00:00:00 2001 From: Chester Wood Date: Sat, 26 Jan 2013 14:52:37 -0700 Subject: [PATCH 2/5] Merge branch 'emacs-mode' --- lib/ace/keyboard/emacs.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 3ab016d0..93c0b2e6 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -66,8 +66,6 @@ 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;\ @@ -94,12 +92,21 @@ exports.handler.attach = function(editor) { }', 'emacsMode' ); } + // in emacs, gotowordleft/right should not count a space as a word.. + editor.session.$selectLongWords = true; + markMode = false; editor.on("click",$resetMarkMode); editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; + // kitchen sink seems to reload the session after loading the kb handler. + editor.on("changeSession",function(e) { + e.session.$selectLongWords = true; + }); + editor.setStyle("emacs-mode"); + }; exports.handler.detach = function(editor) { @@ -323,7 +330,7 @@ exports.handler.addCommands({ selectRectangularRegion: function(editor) { editor.multiSelect.toggleBlockSelection(); }, - setMark: function() { + setMark: function(editor) { // Sets mark-mode and clears current selection. // When in mark-mode, "goto" commands become "select" commands. // Any insertion or mouse click resets mark-mode. @@ -374,9 +381,10 @@ exports.handler.addCommands({ }, killLine: function(editor) { markMode = null; + var pos = editor.getCursorPosition(); - if (editor.getCursorPosition().column == 0 && - editor.selection.isEmpty) { + if (pos.column == 0 && + editor.session.doc.getLine(pos.row).length == 0) { // If an already empty line is killed, remove // the line entirely editor.selection.selectLine(); From 9241e3269c374162eb9f67ecd5de2ebda2db1553 Mon Sep 17 00:00:00 2001 From: Chester Wood Date: Sun, 27 Jan 2013 13:00:29 -0700 Subject: [PATCH 3/5] restore emacs screenToTextCoordinates --- lib/ace/keyboard/emacs.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 93c0b2e6..b0ef90ca 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -105,14 +105,19 @@ exports.handler.attach = function(editor) { e.session.$selectLongWords = true; }); + editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; + editor.setStyle("emacs-mode"); }; exports.handler.detach = function(editor) { + delete editor.renderer.screenToTextCoordinates; - editor.unsetStyle("emacs-mode"); + editor.removeEventListener("click",$resetMarkMode); + + editor.unsetStyle("emacs-mode"); }; var $resetMarkMode = function(e) { From c955ace32acf822e59e4eb665f5ec862a28013ec Mon Sep 17 00:00:00 2001 From: Chester Wood Date: Mon, 28 Jan 2013 12:03:06 -0700 Subject: [PATCH 4/5] make emacs mark a property of session --- lib/ace/keyboard/emacs.js | 46 ++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index b0ef90ca..4d3dfb7b 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -51,17 +51,6 @@ 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; @@ -95,7 +84,13 @@ exports.handler.attach = function(editor) { // in emacs, gotowordleft/right should not count a space as a word.. editor.session.$selectLongWords = true; - markMode = false; + exports.markMode = function() { + return editor.session.$emacsMark; + } + + exports.setMarkMode = function(p) { + editor.session.$emacsMark = p; + } editor.on("click",$resetMarkMode); @@ -103,6 +98,9 @@ exports.handler.attach = function(editor) { // kitchen sink seems to reload the session after loading the kb handler. editor.on("changeSession",function(e) { e.session.$selectLongWords = true; + if (! e.session.hasOwnProperty('$emacsMark')) { + e.session.$emacsMark = false; + } }); editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; @@ -121,7 +119,7 @@ exports.handler.detach = function(editor) { }; var $resetMarkMode = function(e) { - markMode = null; + e.editor.session.$emacsMark = null; } var keys = require("../lib/keys").KEY_MODS; @@ -153,7 +151,7 @@ exports.handler.bindKey = function(key, command) { exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { if (hashId == -1) { - markMode = null; + exports.setMarkMode(null); if (data.count) { var str = Array(data.count + 1).join(key); data.count = null; @@ -199,7 +197,7 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { command = command.command; if (command == "goorselect") { command = args[0]; - if (markMode) { + if (exports.markMode()) { command = args[1]; } args = null; @@ -210,7 +208,7 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { if (command == "insertstring" || command == "splitline" || command == "togglecomment") { - markMode = null; + exports.setMarkMode(null); } command = this.commands[command] || data.editor.commands.commands[command]; } @@ -336,25 +334,29 @@ exports.handler.addCommands({ editor.multiSelect.toggleBlockSelection(); }, setMark: function(editor) { + // Emulate emacs highlighting behaviour in transient-mark-mode. // Sets mark-mode and clears current selection. - // When in mark-mode, "goto" commands become "select" commands. + // 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 = exports.markMode(); 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"); + exports.setMarkMode(null); + // console.log("Mark mode off"); return; } } // turn on mark mode markMode = editor.getCursorPosition(); + exports.setMarkMode(markMode); editor.selection.setSelectionAnchor(markMode.row, markMode.column); }, @@ -385,7 +387,7 @@ exports.handler.addCommands({ multiselectAction: "forEach" }, killLine: function(editor) { - markMode = null; + exports.setMarkMode(null); var pos = editor.getCursorPosition(); if (pos.column == 0 && From 3c4bd8d21ab15c57ecdfad799446770c987b0909 Mon Sep 17 00:00:00 2001 From: Chester Wood Date: Mon, 28 Jan 2013 13:13:42 -0700 Subject: [PATCH 5/5] Fix CTRL-a to work as expected in emacs mode; restore former values of selection settings when switching keyboard or session --- demo/kitchen-sink/demo.js | 2 +- lib/ace/keyboard/emacs.js | 37 +++++++++++++++++++++++++++++-------- lib/ace/selection.js | 15 ++++----------- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 9ae70f4f..4bc1c35e 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -407,7 +407,7 @@ event.addListener(container, "drop", function(e) { if (window.FileReader) { var reader = new FileReader(); reader.onload = function() { - var mode = getModeFromPath(file.name); + var mode = modelist.getModeFromPath(file.name); env.editor.session.doc.setValue(reader.result); modeEl.value = mode.name; diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 4d3dfb7b..d6c54b72 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -50,6 +50,8 @@ var HashHandler = require("./hash_handler").HashHandler; exports.handler = new HashHandler(); var initialized = false; +var $formerLongWords; +var $formerLineStart; exports.handler.attach = function(editor) { if (!initialized) { @@ -82,7 +84,13 @@ exports.handler.attach = function(editor) { ); } // in emacs, gotowordleft/right should not count a space as a word.. + $formerLongWords = editor.session.$selectLongWords; editor.session.$selectLongWords = true; + // CTRL-A should go to actual beginning of line + $formerLineStart = editor.session.$useEmacsStyleLineStart; + editor.session.$useEmacsStyleLineStart = true; + + editor.session.$emacsMark = null; exports.markMode = function() { return editor.session.$emacsMark; @@ -94,14 +102,7 @@ exports.handler.attach = function(editor) { editor.on("click",$resetMarkMode); - editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; - // kitchen sink seems to reload the session after loading the kb handler. - editor.on("changeSession",function(e) { - e.session.$selectLongWords = true; - if (! e.session.hasOwnProperty('$emacsMark')) { - e.session.$emacsMark = false; - } - }); + editor.on("changeSession",$kbSessionChange); editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; @@ -113,11 +114,31 @@ exports.handler.detach = function(editor) { delete editor.renderer.screenToTextCoordinates; + editor.session.$selectLongWords = $formerLongWords; + editor.session.$useEmacsStyleLineStart = $formerLineStart; + + editor.removeEventListener("click",$resetMarkMode); + editor.removeEventListener("changeSession",$kbSessionChange); editor.unsetStyle("emacs-mode"); }; +var $kbSessionChange = function(e) { + if (e.oldSession) { + e.oldSession.$selectLongWords = $formerLongWords; + e.oldSession.$useEmacsStyleLineStart = $formerLineStart; + } + + $formerLongWords = e.session.$selectLongWords; + e.session.$selectLongWords = true; + $formerLineStart = e.session.$useEmacsStyleLineStart; + e.session.$useEmacsStyleLineStart = true; + + if (!e.session.hasOwnProperty('$emacsMark')) + e.session.$emacsMark = null; +} + var $resetMarkMode = function(e) { e.editor.session.$emacsMark = null; } diff --git a/lib/ace/selection.js b/lib/ace/selection.js index d80f9662..dd5435f1 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -517,17 +517,10 @@ var Selection = function(session) { ); var leadingSpace = beforeCursor.match(/^\s*/); - if (leadingSpace[0].length == column) { - this.moveCursorTo( - firstColumnPosition.row, firstColumnPosition.column - ); - } - else { - this.moveCursorTo( - firstColumnPosition.row, - firstColumnPosition.column + leadingSpace[0].length - ); - } + // TODO find better way for emacs mode to override selection behaviors + if (leadingSpace[0].length != column && !this.session.$useEmacsStyleLineStart) + firstColumnPosition.column += leadingSpace[0].length; + this.moveCursorToPosition(firstColumnPosition); }; /**