From b16bc6abc462e70e3b39952fd0bb74affbb90f8d Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 23 Dec 2010 00:06:17 +0100 Subject: [PATCH] Add basic emacs keybinding. --- lib/ace/commands/default_commands.js | 8 ++ lib/ace/keybinding.js | 20 +++- lib/ace/keyboardstate.js | 41 +++++++- lib/ace/mode/emacs.js | 152 +++++++++++++++++++++++++++ lib/ace/mode/vim.js | 19 +--- 5 files changed, 217 insertions(+), 23 deletions(-) create mode 100644 lib/ace/mode/emacs.js diff --git a/lib/ace/commands/default_commands.js b/lib/ace/commands/default_commands.js index 1cd5c1b0..e02dd9bd 100644 --- a/lib/ace/commands/default_commands.js +++ b/lib/ace/commands/default_commands.js @@ -38,6 +38,7 @@ define(function(require, exports, module) { +var lang = require("pilot/lang"); var canon = require("pilot/canon"); canon.addCommand({ @@ -249,5 +250,12 @@ canon.addCommand({ name: "indent", exec: function(env, args, request) { env.editor.indent(); } }); +canon.addCommand({ + name: "inserttext", + exec: function(env, args, request) { + env.editor.onTextInput(lang.stringRepeat(args.text || "", + args.times || 1)); + } +}); }); diff --git a/lib/ace/keybinding.js b/lib/ace/keybinding.js index ba005f30..35699456 100644 --- a/lib/ace/keybinding.js +++ b/lib/ace/keybinding.js @@ -42,10 +42,12 @@ var useragent = require("pilot/useragent"); var event = require("pilot/event"); var default_mac = require("ace/conf/keybindings/default_mac").bindings; var default_win = require("ace/conf/keybindings/default_win").bindings; -var vim_mode = require("ace/mode/vim"); var canon = require("pilot/canon"); require("ace/commands/default_commands"); +var vim_mode = require("ace/mode/vim"); +var emacs_mode = require("ace/mode/emacs"); + var KeyBinding = function(element, editor, config) { this.setConfig(config); var data = { }; @@ -60,13 +62,25 @@ var KeyBinding = function(element, editor, config) { var hashId = 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0); - var key = (_self.keyNames[e.keyCode] || + console.log(hashId, e.keyCode); + var key; + var keyCode = e.keyCode; + + // TODO: Pressing the command key on Chrome/Mac causes the keyCode + // to be 91, which is the "[" character, without pressing this actually. + // Invastigate more further and make a proper work around. + if (hashId & 8 && keyCode == 91) { + key = ""; + } else { + key = (_self.keyNames[e.keyCode] || String.fromCharCode(e.keyCode)).toLowerCase(); + } var toExecute; if (true) { var toExecute = - vim_mode.handleKeyboard(data, hashId, key, e); + // vim_mode.handleKeyboard(data, hashId, key, e); + emacs_mode.handleKeyboard(data, hashId, key, e); } // If there is nothing to execute yet, then use the default keymapping. diff --git a/lib/ace/keyboardstate.js b/lib/ace/keyboardstate.js index 0d07b2e8..600447d5 100644 --- a/lib/ace/keyboardstate.js +++ b/lib/ace/keyboardstate.js @@ -80,7 +80,7 @@ KeyboardStateMapper.prototype = { if (hashId & 8) keyArray.push("Command"); if (hashId & 2) keyArray.push("Option"); if (hashId & 4) keyArray.push("Shift"); - keyArray.push(key); + if (key) keyArray.push(key); var symbolicName = keyArray.join("-").toLowerCase(); var bufferToUse = data.buffer + symbolicName; @@ -130,7 +130,7 @@ KeyboardStateMapper.prototype = { if (binding.disallowMatches) { for (var i = 0; i < binding.disallowMatches.length; i++) { if (!!match[binding.disallowMatches[i]]) { - return true; + return false; } } } @@ -172,10 +172,16 @@ KeyboardStateMapper.prototype = { result.command = "null"; } + console.log("KeyboardStateMapper#find", binding); return true; }); - return result.command ? result : false; + if (result.command) { + return result; + } else { + data.buffer = ""; + return false; + } }, match: function(data, hashId, key) { @@ -191,5 +197,32 @@ KeyboardStateMapper.prototype = { } } +/** + * This is a useful matching function and therefore is defined here so that + * users of KeyboardStateMapper can use it. + * + * @return boolean + * If no command key (Command|Option|Shift|Ctrl) is pressed, it + * returns true. If the only the Shift key is pressed + a character + * true is returned as well. Otherwise, false is returned. + * Summing up, the function returns true whenever the user typed + * a normal character on the keyboard and no shortcut. + */ +exports.matchCharacterOnly = function(buffer, hashId, key, symbolicName) { + // If no command keys are pressed, then catch the input. + if (hashId == 0) { + return true; + } + // If only the shift key is pressed and a character key, then + // catch that input as well. + else if ((hashId == 4) && key.length == 1) { + return true; + } + // Otherwise, we let the input got through. + else { + return false; + } +}; + exports.KeyboardStateMapper = KeyboardStateMapper; -}); \ No newline at end of file +}); diff --git a/lib/ace/mode/emacs.js b/lib/ace/mode/emacs.js new file mode 100644 index 00000000..6cbf68dc --- /dev/null +++ b/lib/ace/mode/emacs.js @@ -0,0 +1,152 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Skywriter. + * + * The Initial Developer of the Original Code is + * Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Julian Viereck (julian.viereck@gmail.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + +var KeyboardStateMapper = require("ace/keyboardstate").KeyboardStateMapper; +var matchCharacterOnly = require("ace/keyboardstate").matchCharacterOnly; + +var emacsState = { + start: [ + { + key: "ctrl-x", + then: "c-x" + }, + { + regex: [ "(?:command-([0-9]*))*", "(down|ctrl-n)" ], + exec: "golinedown", + params: [ + { + name: "times", + match: 1, + type: "number", + defaultValue: 1 + } + ] + }, + { + regex: [ "(?:command-([0-9]*))*", "(right|ctrl-f)" ], + exec: "gotoright", + params: [ + { + name: "times", + match: 1, + type: "number", + defaultValue: 1 + } + ] + }, + { + regex: [ "(?:command-([0-9]*))*", "(up|ctrl-p)" ], + exec: "golineup", + params: [ + { + name: "times", + match: 1, + type: "number", + defaultValue: 1 + } + ] + }, + { + regex: [ "(?:command-([0-9]*))*", "(left|ctrl-b)" ], + exec: "gotoleft", + params: [ + { + name: "times", + match: 1, + type: "number", + defaultValue: 1 + } + ] + }, + { + comment: "This binding matches all printable characters except numbers as long as they are no numbers and print them n times.", + regex: [ "(?:command-([0-9]*))", "([^0-9]+)*" ], + match: matchCharacterOnly, + exec: "inserttext", + params: [ + { + name: "times", + match: 1, + type: "number", + defaultValue: "1" + }, + { + name: "text", + match: 2 + } + ] + }, + { + comment: "This binding matches numbers as long as there is no meta_number in the buffer.", + regex: [ "(command-[0-9]*)*", "([0-9]+)" ], + match: matchCharacterOnly, + disallowMatches: [ 1 ], + exec: "inserttext", + params: [ + { + name: "text", + match: 2, + type: "text" + } + ] + }, + { + regex: [ "command-([0-9]*)", "(command-[0-9]|[0-9])" ], + comment: "Stops execution if the regex /meta_[0-9]+/ matches to avoid resetting the buffer." + } + ], + "c-x": [ + { + key: "ctrl-g", + then: "start" + }, + { + key: "ctrl-s", + exec: "save", + then: "start" + } + ] +}; + +var emacsKeyboardStateMapper = new KeyboardStateMapper(emacsState); + +exports.handleKeyboard = function(data, hashId, key, e) { + return emacsKeyboardStateMapper.match(data, hashId, key); +} +}); diff --git a/lib/ace/mode/vim.js b/lib/ace/mode/vim.js index eb36635d..1cb817ee 100644 --- a/lib/ace/mode/vim.js +++ b/lib/ace/mode/vim.js @@ -38,6 +38,7 @@ define(function(require, exports, module) { var KeyboardStateMapper = require("ace/keyboardstate").KeyboardStateMapper; +var matchCharacterOnly = require("ace/keyboardstate").matchCharacterOnly; var vimStates = { start: [ @@ -95,21 +96,7 @@ var vimStates = { }, { comment: "Catch some keyboard input to stop it here", - match: function(buffer, hashId, key, symbolicName) { - // If no command keys are pressed, then catch the input. - if (hashId == 0) { - return true; - } - // If only the shift key is pressed and a character key, then - // catch that input as well. - else if ((hashId == 4) && key.length == 1) { - return true; - } - // Otherwise, we let the input got through. - else { - return false; - } - } + match: matchCharacterOnly } ], insertMode: [ @@ -125,4 +112,4 @@ var vimKeyboardStateMapper = new KeyboardStateMapper(vimStates); exports.handleKeyboard = function(data, hashId, key, e) { return vimKeyboardStateMapper.match(data, hashId, key); } -}); \ No newline at end of file +});