diff --git a/keymap/vim.js b/keymap/vim.js index c5b945cd675..7206fe6442e 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -77,11 +77,11 @@ { keys: '', type: 'keyToKey', toKeys: 'k' }, { keys: '', type: 'keyToKey', toKeys: 'j' }, { keys: '', type: 'keyToKey', toKeys: 'l' }, - { keys: '', type: 'keyToKey', toKeys: 'h' }, + { keys: '', type: 'keyToKey', toKeys: 'h', context: 'normal'}, { keys: '', type: 'keyToKey', toKeys: 'W' }, - { keys: '', type: 'keyToKey', toKeys: 'B' }, + { keys: '', type: 'keyToKey', toKeys: 'B', context: 'normal' }, { keys: '', type: 'keyToKey', toKeys: 'w' }, - { keys: '', type: 'keyToKey', toKeys: 'b' }, + { keys: '', type: 'keyToKey', toKeys: 'b', context: 'normal' }, { keys: '', type: 'keyToKey', toKeys: 'j' }, { keys: '', type: 'keyToKey', toKeys: 'k' }, { keys: '', type: 'keyToKey', toKeys: '' }, @@ -233,51 +233,6 @@ var specialKey = {Enter:'CR',Backspace:'BS',Delete:'Del'}; var mac = /Mac/.test(navigator.platform); var Vim = function() { - function lookupKey(e) { - var keyCode = e.keyCode; - if (modifierCodes.indexOf(keyCode) != -1) { return; } - var hasModifier = e.ctrlKey || e.metaKey; - var key = CodeMirror.keyNames[keyCode]; - key = specialKey[key] || key; - var name = ''; - if (e.ctrlKey) { name += 'C-'; } - if (e.altKey) { name += 'A-'; } - if (mac && e.metaKey || (!hasModifier && e.shiftKey) && key.length < 2) { - // Shift key bindings can only specified for special characters. - return; - } else if (e.shiftKey && !/^[A-Za-z]$/.test(key)) { - name += 'S-'; - } - if (key.length == 1) { key = key.toLowerCase(); } - name += key; - if (name.length > 1) { name = '<' + name + '>'; } - return name; - } - // Keys with modifiers are handled using keydown due to limitations of - // keypress event. - function handleKeyDown(cm, e) { - var name = lookupKey(e); - if (!name) { return; } - - CodeMirror.signal(cm, 'vim-keypress', name); - if (CodeMirror.Vim.handleKey(cm, name, 'user')) { - CodeMirror.e_stop(e); - } - } - // Keys without modifiers are handled using keypress to work best with - // non-standard keyboard layouts. - function handleKeyPress(cm, e) { - var code = e.charCode || e.keyCode; - if (e.ctrlKey || e.metaKey || e.altKey || - e.shiftKey && code < 32) { return; } - var name = String.fromCharCode(code); - - CodeMirror.signal(cm, 'vim-keypress', name); - if (CodeMirror.Vim.handleKey(cm, name, 'user')) { - CodeMirror.e_stop(e); - } - } - function enterVimMode(cm) { cm.setOption('disableInput', true); cm.setOption('showCursorWhenSelecting', false); @@ -285,8 +240,6 @@ cm.on('cursorActivity', onCursorActivity); maybeInitVimState(cm); CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); - cm.on('keypress', handleKeyPress); - cm.on('keydown', handleKeyDown); } function leaveVimMode(cm) { @@ -294,8 +247,6 @@ cm.off('cursorActivity', onCursorActivity); CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); cm.state.vim = null; - cm.off('keypress', handleKeyPress); - cm.off('keydown', handleKeyDown); } function detachVimMap(cm, next) { @@ -320,6 +271,53 @@ else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap"))) cm.setOption("keyMap", "default"); }); + + function cmKey(key, cm) { + if (!cm) { return undefined; } + var vimKey = cmKeyToVimKey(key); + if (!vimKey) { + return false; + } + var cmd = CodeMirror.Vim.findKey(cm, vimKey); + if (typeof cmd == 'function') { + CodeMirror.signal(cm, 'vim-keypress', name); + } + return cmd; + } + + var modifiers = {'Shift': 'S', 'Ctrl': 'C', 'Alt': 'A', 'Cmd': 'D', 'Mod': 'A'}; + var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del'}; + function cmKeyToVimKey(key) { + if (key.charAt(0) == '\'') { + // Keypress character binding of format "'a'" + return key.charAt(1); + } + var pieces = key.split('-'); + if (pieces.length == 1 && pieces[0].length == 1) { + // No-modifier bindings use keypress character bindings above. Skip. + return false; + } + var hasModifier = pieces.length > 1; + var hasCharacter = false; + for (var i = 0; i < pieces.length; i++) { + var piece = pieces[i]; + if (piece in modifiers) { pieces[i] = modifiers[piece]; } + else { hasCharacter = true; } + if (piece in specialKeys) { pieces[i] = specialKeys[piece]; } + } + if (!hasCharacter) { + // Vim does not support modifier only keys. + return false; + } + // TODO: Current bindings expect the character to be lower case, but + // it looks like vim key notation uses upper case. + var lastPiece = pieces[pieces.length - 1]; + if (isUpperCase(lastPiece)) { + pieces[pieces.length - 1] = lastPiece.toLowerCase(); + } + return '<' + pieces.join('-') + '>'; + } + function getOnPasteFn(cm) { var vim = cm.state.vim; if (!vim.onPasteFn) { @@ -629,9 +627,22 @@ exCommands[name]=func; exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'}; }, - // This is the outermost function called by CodeMirror, after keys have - // been mapped to their Vim equivalents. - handleKey: function(cm, key, origin) { + handleKey: function (cm, key, origin) { + var command = this.findKey(cm, key, origin); + if (typeof command === 'function') { + return command(); + } + }, + /** + * This is the outermost function called by CodeMirror, after keys have + * been mapped to their Vim equivalents. + * + * Finds a command based on the key (and cached keys if there is a + * multi-key sequence). Returns `false` if no key is matched, `true` if + * a partial match is found (multi-key), and a function to execute the + * bound command if a a key is matched. The function always returns true. + */ + findKey: function(cm, key, origin) { var vim = maybeInitVimState(cm); function handleMacroRecording() { var macroModeState = vimGlobalState.macroModeState; @@ -697,13 +708,7 @@ cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input'); } clearInputState(cm); - var command = match.command; - if (command.type == 'keyToKey') { - doKeyToKey(command.toKeys); - } else { - commandDispatcher.processCommand(cm, vim, command); - } - return true; + return match.command; } function handleKeyNonInsertMode() { @@ -721,31 +726,42 @@ else if (match.type == 'partial') { return true; } vim.inputState.keyBuffer = ''; - var command = match.command; var keysMatcher = /^(\d*)(.*)$/.exec(keys); if (keysMatcher[1] && keysMatcher[1] != '0') { vim.inputState.pushRepeatDigit(keysMatcher[1]); } - if (command.type == 'keyToKey') { - doKeyToKey(command.toKeys); - } else { - commandDispatcher.processCommand(cm, vim, command); - } - return true; + return match.command; } - return cm.operation(function() { - cm.curOp.isVimOp = true; - try { - if (vim.insertMode) { return handleKeyInsertMode(); } - else { return handleKeyNonInsertMode(); } - } catch (e) { - // clear VIM state in case it's in a bad state. - cm.state.vim = undefined; - maybeInitVimState(cm); - throw e; - } - }); + var command; + if (vim.insertMode) { command = handleKeyInsertMode(); } + else { command = handleKeyNonInsertMode(); } + if (command === false) { + return undefined; + } else if (command === true) { + // TODO: Look into using CodeMirror's multi-key handling. + // Return no-op since we are caching the key. Counts as handled, but + // don't want act on it just yet. + return function() {}; + } else { + return function() { + cm.operation(function() { + cm.curOp.isVimOp = true; + try { + if (command.type == 'keyToKey') { + doKeyToKey(command.toKeys); + } else { + commandDispatcher.processCommand(cm, vim, command); + } + } catch (e) { + // clear VIM state in case it's in a bad state. + cm.state.vim = undefined; + maybeInitVimState(cm); + throw e; + } + return true; + })}; + } }, handleEx: function(cm, input) { exCommandDispatcher.processCommand(cm, input); @@ -4476,7 +4492,8 @@ CodeMirror.keyMap.vim = { attach: attachVimMap, - detach: detachVimMap + detach: detachVimMap, + call: cmKey }; function exitInsertMode(cm) { @@ -4549,20 +4566,16 @@ }, fallthrough: ['default'], attach: attachVimMap, - detach: detachVimMap - }; - - CodeMirror.keyMap['await-second'] = { - fallthrough: ['vim-insert'], - attach: attachVimMap, - detach: detachVimMap + detach: detachVimMap, + call: cmKey }; CodeMirror.keyMap['vim-replace'] = { 'Backspace': 'goCharLeft', fallthrough: ['vim-insert'], attach: attachVimMap, - detach: detachVimMap + detach: detachVimMap, + call: cmKey }; function executeMacroRegister(cm, vim, macroModeState, registerName) {