From 53a2f12128644b181ee85ccd6e5791aa002d9c91 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 19 Feb 2013 12:18:24 -0800 Subject: [PATCH 1/7] cleaning up trailing whitespace --- lib/ace/keyboard/emacs.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index d6c54b72..4a4a6b8c 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -3,7 +3,7 @@ * * Copyright (c) 2010, Ajax.org B.V. * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright @@ -14,7 +14,7 @@ * * Neither the name of Ajax.org B.V. nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -89,9 +89,9 @@ exports.handler.attach = function(editor) { // 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; } @@ -129,7 +129,7 @@ var $kbSessionChange = function(e) { e.oldSession.$selectLongWords = $formerLongWords; e.oldSession.$useEmacsStyleLineStart = $formerLineStart; } - + $formerLongWords = e.session.$selectLongWords; e.session.$selectLongWords = true; $formerLineStart = e.session.$useEmacsStyleLineStart; @@ -137,7 +137,7 @@ var $kbSessionChange = function(e) { if (!e.session.hasOwnProperty('$emacsMark')) e.session.$emacsMark = null; -} +} var $resetMarkMode = function(e) { e.editor.session.$emacsMark = null; @@ -256,11 +256,11 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { exports.emacsKeys = { // movement - "Up|C-p" : {command: "goorselect", args: ["golineup","selectup"]}, - "Down|C-n" : {command: "goorselect", args: ["golinedown","selectdown"]}, + "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-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"]}, @@ -320,7 +320,7 @@ exports.emacsKeys = { "M-u": "touppercase", // Doesn't work "M-l": "tolowercase", "M-/": "autocomplete", // Doesn't work - "C-u": "universalArgument", + "C-u": "universalArgument", "M-;": "togglecomment", @@ -340,7 +340,7 @@ exports.handler.addCommands({ recenterTopBottom: function(editor) { var renderer = editor.renderer; var pos = renderer.$cursorLayer.getPixelPosition(); - var h = renderer.$size.scrollerHeight - renderer.lineHeight; + var h = renderer.$size.scrollerHeight - renderer.lineHeight; var scrollTop = renderer.scrollTop; if (Math.abs(pos.top - scrollTop) < 2) { scrollTop = pos.top - h; @@ -357,7 +357,7 @@ exports.handler.addCommands({ 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 + // 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. @@ -379,7 +379,7 @@ exports.handler.addCommands({ markMode = editor.getCursorPosition(); exports.setMarkMode(markMode); editor.selection.setSelectionAnchor(markMode.row, markMode.column); - + }, exchangePointAndMark: { exec: function(editor) { From c76aed967f917798372c6396d45b1af6d4743ad8 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 19 Feb 2013 12:26:05 -0800 Subject: [PATCH 2/7] emacs code cleanup --- lib/ace/keyboard/emacs.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 4a4a6b8c..6439575d 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -363,10 +363,8 @@ exports.handler.addCommands({ // 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(); + var cp = editor.getCursorPosition(); if (editor.selection.isEmpty() && markMode.row == cp.row && markMode.column == cp.column) { exports.setMarkMode(null); @@ -374,7 +372,6 @@ exports.handler.addCommands({ return; } } - // turn on mark mode markMode = editor.getCursorPosition(); exports.setMarkMode(markMode); @@ -384,7 +381,6 @@ exports.handler.addCommands({ exchangePointAndMark: { exec: function(editor) { var range = editor.selection.getRange(); - editor.selection.setSelectionRange(range, !editor.selection.isBackwards()); }, readonly: true, From fa1a88eb7fa015cbd1b867de8e61e35e28030d14 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 19 Feb 2013 12:26:37 -0800 Subject: [PATCH 3/7] emacs: adding missing keyboardQuit command --- lib/ace/keyboard/emacs.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 6439575d..637023f1 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -445,6 +445,10 @@ exports.handler.addCommands({ }, killRingSave: function(editor) { exports.killRing.add(editor.getCopyText()); + }, + keyboardQuit: function(editor) { + editor.selection.clearSelection(); + exports.setMarkMode(null); } }); From 50a6bd21af30c58ee2e567f12ec23e5fcd4feeb8 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 19 Feb 2013 15:15:03 -0800 Subject: [PATCH 4/7] add platform attribute to emacs handler to allow platform-specific keybindings usage like: editor.getKeyboardHandler().addCommand({ name: "...", bindKey: {win: "...", mac: "..."}, exec: function(e) { ... } }); --- lib/ace/keyboard/emacs.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 637023f1..0c69fc91 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -56,7 +56,6 @@ var $formerLineStart; exports.handler.attach = function(editor) { if (!initialized) { initialized = true; - dom.importCssString('\ .emacs-mode .ace_cursor{\ border: 2px rgba(50,250,50,0.8) solid!important;\ @@ -101,26 +100,18 @@ exports.handler.attach = function(editor) { } editor.on("click",$resetMarkMode); - editor.on("changeSession",$kbSessionChange); - editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; - editor.setStyle("emacs-mode"); - + exports.handler.platform = editor.commands.platform; }; 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"); }; @@ -406,7 +397,6 @@ exports.handler.addCommands({ killLine: function(editor) { exports.setMarkMode(null); var pos = editor.getCursorPosition(); - if (pos.column == 0 && editor.session.doc.getLine(pos.row).length == 0) { // If an already empty line is killed, remove @@ -418,7 +408,6 @@ exports.handler.addCommands({ // the cursor) editor.clearSelection(); editor.selection.selectLineEnd(); - } var range = editor.getSelectionRange(); var text = editor.session.getTextRange(range); @@ -434,7 +423,6 @@ exports.handler.addCommands({ yankRotate: function(editor) { if (editor.keyBinding.$data.lastCommand != "yank") return; - editor.undo(); editor.onPaste(exports.killRing.rotate()); editor.keyBinding.$data.lastCommand = "yank"; From 9fd93a5e37f2fa22f03069e21cf99bf045b342fd Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 19 Feb 2013 15:20:11 -0800 Subject: [PATCH 5/7] adding command key to emacs keyhandler. usage: keyHandler.bindKey("CMD-g": "someNewCommand") --- lib/ace/keyboard/emacs.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 0c69fc91..a09119ce 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -134,13 +134,15 @@ var $resetMarkMode = function(e) { e.editor.session.$emacsMark = null; } -var keys = require("../lib/keys").KEY_MODS; -var eMods = { - C: "ctrl", S: "shift", M: "alt" -}; -["S-C-M", "S-C", "S-M", "C-M", "S", "C", "M"].forEach(function(c) { +var keys = require("../lib/keys").KEY_MODS, + eMods = {C: "ctrl", S: "shift", M: "alt", CMD: "command"}, + combinations = ["C-S-M-CMD", + "S-M-CMD", "C-M-CMD", "C-S-CMD", "C-S-M", + "M-CMD", "S-CMD", "S-M", "C-CMD", "C-M", "C-S", + "CMD", "M", "S", "C"]; +combinations.forEach(function(c) { var hashId = 0; - c.split("-").forEach(function(c){ + c.split("-").forEach(function(c) { hashId = hashId | keys[eMods[c]]; }); eMods[hashId] = c.toLowerCase() + "-"; From a82fcd0dc2aa386b25b214d3158a6427315b3971 Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 19 Feb 2013 15:26:58 -0800 Subject: [PATCH 6/7] comments for emacs handleKeyboard --- lib/ace/keyboard/emacs.js | 50 +++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index a09119ce..aee6ff4b 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -164,6 +164,7 @@ exports.handler.bindKey = function(key, command) { exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { + // insertstring data.count times if (hashId == -1) { exports.setMarkMode(null); if (data.count) { @@ -177,6 +178,8 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { return; var modifier = eMods[hashId]; + + // CTRL + number / universalArgument for setting data.count if (modifier == "c-" || data.universalArgument) { var count = parseInt(key[key.length - 1]); if (count) { @@ -186,42 +189,47 @@ exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { } data.universalArgument = false; - if (modifier) - key = modifier + key; + // this.commandKeyBinding maps key specs like "c-p" (for CTRL + P) to + // command objects, for lookup key needs to include the modifier + if (modifier) key = modifier + key; - if (data.keyChain) - key = data.keyChain += " " + key; + // Key combos like CTRL+X H build up the data.keyChain + if (data.keyChain) key = data.keyChain += " " + key; + // Key combo prefixes get stored as "null" (String!) in this + // this.commmandKeyBinding. When encountered no command is invoked but we + // buld up data.keyChain var command = this.commmandKeyBinding[key]; data.keyChain = command == "null" ? key : ""; - if (!command) - return; + // there really is no command + if (!command) return; - if (command == "null") - return {command: "null"}; + // we pass b/c of key combo or universalArgument + if (command === "null") return {command: "null"}; - if (command == "universalArgument") { + if (command === "universalArgument") { data.universalArgument = true; return {command: "null"}; } - if (typeof command != "string") { - var args = command.args; - command = command.command; - if (command == "goorselect") { - command = args[0]; - if (exports.markMode()) { - command = args[1]; - } + // lookup command + // TODO extract special handling of markmode + // TODO special case command.command is really unnecessary, remove + var args; + if (typeof command !== "string") { + args = command.args; + if (command.command) command = command.command; + if (command === "goorselect") { + command = exports.markMode() ? args[1] : args[0]; args = null; } } - if (typeof command == "string") { - if (command == "insertstring" || - command == "splitline" || - command == "togglecomment") { + if (typeof command === "string") { + if (command === "insertstring" || + command === "splitline" || + command === "togglecomment") { exports.setMarkMode(null); } command = this.commands[command] || data.editor.commands.commands[command]; From 241d44f0b955a315028e0e2cca7dca1fc2ebf3af Mon Sep 17 00:00:00 2001 From: Robert Krahn Date: Tue, 19 Feb 2013 17:23:15 -0800 Subject: [PATCH 7/7] adding tests for emacs handler --- lib/ace/keyboard/emacs.js | 2 + lib/ace/keyboard/emacs_test.js | 73 ++++++++++++++++++++++++++++++++++ lib/ace/test/all_browser.js | 1 + 3 files changed, 76 insertions(+) create mode 100644 lib/ace/keyboard/emacs_test.js diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index aee6ff4b..7fb93b0f 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -103,6 +103,7 @@ exports.handler.attach = function(editor) { editor.on("changeSession",$kbSessionChange); editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; editor.setStyle("emacs-mode"); + editor.commands.addCommands(commands); exports.handler.platform = editor.commands.platform; }; @@ -113,6 +114,7 @@ exports.handler.detach = function(editor) { editor.removeEventListener("click",$resetMarkMode); editor.removeEventListener("changeSession",$kbSessionChange); editor.unsetStyle("emacs-mode"); + editor.commands.removeCommands(commands); }; var $kbSessionChange = function(e) { diff --git a/lib/ace/keyboard/emacs_test.js b/lib/ace/keyboard/emacs_test.js new file mode 100644 index 00000000..d1aba564 --- /dev/null +++ b/lib/ace/keyboard/emacs_test.js @@ -0,0 +1,73 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +if (typeof process !== "undefined") { + require("amd-loader"); +} + +define(function(require, exports, module) { +"use strict"; + +var EditSession = require("./../edit_session").EditSession, + Editor = require("./../editor").Editor, + MockRenderer = require("./../test/mockrenderer").MockRenderer, + emacs = require('./emacs'), + assert = require("./../test/assertions"), + editor; + +function initEditor(docString) { + var doc = new EditSession(docString.split("\n")); + editor = new Editor(new MockRenderer(), doc); + editor.setKeyboardHandler(emacs.handler); +} + +module.exports = { + + "test: detach removes emacs commands from command manager": function() { + initEditor(''); + assert.ok(!!editor.commands.byName["keyboardQuit"], 'setup error: emacs commands not installed'); + editor.keyBinding.removeKeyboardHandler(editor.getKeyboardHandler()); + assert.ok(!editor.commands.byName["keyboardQuit"], 'emacs commands not removed'); + }, + + "test: keyboardQuit clears selection": function() { + initEditor('foo'); + editor.selectAll(); + editor.execCommand('keyboardQuit'); + assert.ok(editor.selection.isEmpty(), 'selection non-empty'); + } + +}; + +}); + +if (typeof module !== "undefined" && module === require.main) { + require("asyncjs").test.testcase(module.exports).exec() +} diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index 9437426a..ce5857dc 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -21,6 +21,7 @@ var testNames = [ "ace/editor_navigation_test", "ace/editor_text_edit_test", "ace/ext/static_highlight_test", + "ace/keyboard/emacs_test", "ace/layer/text_test", "ace/lib/event_emitter_test", "ace/mode/coffee/parser_test",