tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

vim.js (215589B)


      1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
      2 // Distributed under an MIT license: https://codemirror.net/LICENSE
      3 
      4 /**
      5 * Supported keybindings:
      6 *   Too many to list. Refer to defaultKeymap below.
      7 *
      8 * Supported Ex commands:
      9 *   Refer to defaultExCommandMap below.
     10 *
     11 * Registers: unnamed, -, a-z, A-Z, 0-9
     12 *   (Does not respect the special case for number registers when delete
     13 *    operator is made with these commands: %, (, ),  , /, ?, n, N, {, } )
     14 *   TODO: Implement the remaining registers.
     15 *
     16 * Marks: a-z, A-Z, and 0-9
     17 *   TODO: Implement the remaining special marks. They have more complex
     18 *       behavior.
     19 *
     20 * Events:
     21 *  'vim-mode-change' - raised on the editor anytime the current mode changes,
     22 *                      Event object: {mode: "visual", subMode: "linewise"}
     23 *
     24 * Code structure:
     25 *  1. Default keymap
     26 *  2. Variable declarations and short basic helpers
     27 *  3. Instance (External API) implementation
     28 *  4. Internal state tracking objects (input state, counter) implementation
     29 *     and instantiation
     30 *  5. Key handler (the main command dispatcher) implementation
     31 *  6. Motion, operator, and action implementations
     32 *  7. Helper functions for the key handler, motions, operators, and actions
     33 *  8. Set up Vim to work as a keymap for CodeMirror.
     34 *  9. Ex command implementations.
     35 */
     36 
     37 (function(mod) {
     38  if (typeof exports == "object" && typeof module == "object") // CommonJS
     39    mod(require("resource://devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js"), require("resource://devtools/client/shared/sourceeditor/codemirror/addon/search/searchcursor.js"), require("resource://devtools/client/shared/sourceeditor/codemirror/addon/dialog/dialog.js"), require("resource://devtools/client/shared/sourceeditor/codemirror/addon/edit/matchbrackets.js"));
     40  else if (typeof define == "function" && define.amd) // AMD
     41    define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog", "../addon/edit/matchbrackets"], mod);
     42  else // Plain browser env
     43    mod(CodeMirror);
     44 })(function(CodeMirror) {
     45  'use strict';
     46 
     47  var defaultKeymap = [
     48    // Key to key mapping. This goes first to make it possible to override
     49    // existing mappings.
     50    { keys: '<Left>', type: 'keyToKey', toKeys: 'h' },
     51    { keys: '<Right>', type: 'keyToKey', toKeys: 'l' },
     52    { keys: '<Up>', type: 'keyToKey', toKeys: 'k' },
     53    { keys: '<Down>', type: 'keyToKey', toKeys: 'j' },
     54    { keys: '<Space>', type: 'keyToKey', toKeys: 'l' },
     55    { keys: '<BS>', type: 'keyToKey', toKeys: 'h', context: 'normal'},
     56    { keys: '<Del>', type: 'keyToKey', toKeys: 'x', context: 'normal'},
     57    { keys: '<C-Space>', type: 'keyToKey', toKeys: 'W' },
     58    { keys: '<C-BS>', type: 'keyToKey', toKeys: 'B', context: 'normal' },
     59    { keys: '<S-Space>', type: 'keyToKey', toKeys: 'w' },
     60    { keys: '<S-BS>', type: 'keyToKey', toKeys: 'b', context: 'normal' },
     61    { keys: '<C-n>', type: 'keyToKey', toKeys: 'j' },
     62    { keys: '<C-p>', type: 'keyToKey', toKeys: 'k' },
     63    { keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>' },
     64    { keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>' },
     65    { keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },
     66    { keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },
     67    { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' },
     68    { keys: 's', type: 'keyToKey', toKeys: 'c', context: 'visual'},
     69    { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' },
     70    { keys: 'S', type: 'keyToKey', toKeys: 'VdO', context: 'visual' },
     71    { keys: '<Home>', type: 'keyToKey', toKeys: '0' },
     72    { keys: '<End>', type: 'keyToKey', toKeys: '$' },
     73    { keys: '<PageUp>', type: 'keyToKey', toKeys: '<C-b>' },
     74    { keys: '<PageDown>', type: 'keyToKey', toKeys: '<C-f>' },
     75    { keys: '<CR>', type: 'keyToKey', toKeys: 'j^', context: 'normal' },
     76    { keys: '<Ins>', type: 'action', action: 'toggleOverwrite', context: 'insert' },
     77    // Motions
     78    { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }},
     79    { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }},
     80    { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }},
     81    { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }},
     82    { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }},
     83    { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }},
     84    { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }},
     85    { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }},
     86    { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }},
     87    { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }},
     88    { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }},
     89    { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }},
     90    { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }},
     91    { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }},
     92    { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }},
     93    { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }},
     94    { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }},
     95    { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }},
     96    { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }},
     97    { keys: '(', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: false }},
     98    { keys: ')', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: true }},
     99    { keys: '<C-f>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }},
    100    { keys: '<C-b>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }},
    101    { keys: '<C-d>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }},
    102    { keys: '<C-u>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }},
    103    { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
    104    { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
    105    { keys: '0', type: 'motion', motion: 'moveToStartOfLine' },
    106    { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' },
    107    { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }},
    108    { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }},
    109    { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
    110    { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }},
    111    { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }},
    112    { keys: 'f<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }},
    113    { keys: 'F<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }},
    114    { keys: 't<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }},
    115    { keys: 'T<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }},
    116    { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }},
    117    { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }},
    118    { keys: '\'<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}},
    119    { keys: '`<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}},
    120    { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
    121    { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
    122    { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
    123    { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
    124    // the next two aren't motions but must come before more general motion declarations
    125    { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}},
    126    { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}},
    127    { keys: ']<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}},
    128    { keys: '[<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}},
    129    { keys: '|', type: 'motion', motion: 'moveToColumn'},
    130    { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'},
    131    { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'},
    132    // Operators
    133    { keys: 'd', type: 'operator', operator: 'delete' },
    134    { keys: 'y', type: 'operator', operator: 'yank' },
    135    { keys: 'c', type: 'operator', operator: 'change' },
    136    { keys: '=', type: 'operator', operator: 'indentAuto' },
    137    { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }},
    138    { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }},
    139    { keys: 'g~', type: 'operator', operator: 'changeCase' },
    140    { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true },
    141    { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true },
    142    { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }},
    143    { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }},
    144    // Operator-Motion dual commands
    145    { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }},
    146    { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }},
    147    { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
    148    { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'},
    149    { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'expandToLine', motionArgs: { linewise: true }, context: 'normal'},
    150    { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'},
    151    { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
    152    { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'},
    153    { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'},
    154    { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'},
    155    { keys: '<C-w>', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },
    156    //ignore C-w in normal mode
    157    { keys: '<C-w>', type: 'idle', context: 'normal' },
    158    // Actions
    159    { keys: '<C-i>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},
    160    { keys: '<C-o>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }},
    161    { keys: '<C-e>', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }},
    162    { keys: '<C-y>', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }},
    163    { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' },
    164    { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
    165    { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
    166    { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
    167    { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' },
    168    { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' },
    169    { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
    170    { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
    171    { keys: 'v', type: 'action', action: 'toggleVisualMode' },
    172    { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }},
    173    { keys: '<C-v>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
    174    { keys: '<C-q>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
    175    { keys: 'gv', type: 'action', action: 'reselectLastSelection' },
    176    { keys: 'J', type: 'action', action: 'joinLines', isEdit: true },
    177    { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }},
    178    { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }},
    179    { keys: 'r<character>', type: 'action', action: 'replace', isEdit: true },
    180    { keys: '@<character>', type: 'action', action: 'replayMacro' },
    181    { keys: 'q<character>', type: 'action', action: 'enterMacroRecordMode' },
    182    // Handle Replace-mode as a special case of insert mode.
    183    { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }},
    184    { keys: 'u', type: 'action', action: 'undo', context: 'normal' },
    185    { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true },
    186    { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true },
    187    { keys: '<C-r>', type: 'action', action: 'redo' },
    188    { keys: 'm<character>', type: 'action', action: 'setMark' },
    189    { keys: '"<character>', type: 'action', action: 'setRegister' },
    190    { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }},
    191    { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
    192    { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }},
    193    { keys: 'z<CR>', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
    194    { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }},
    195    { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
    196    { keys: '.', type: 'action', action: 'repeatLastEdit' },
    197    { keys: '<C-a>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}},
    198    { keys: '<C-x>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}},
    199    { keys: '<C-t>', type: 'action', action: 'indent', actionArgs: { indentRight: true }, context: 'insert' },
    200    { keys: '<C-d>', type: 'action', action: 'indent', actionArgs: { indentRight: false }, context: 'insert' },
    201    // Text object motions
    202    { keys: 'a<character>', type: 'motion', motion: 'textObjectManipulation' },
    203    { keys: 'i<character>', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }},
    204    // Search
    205    { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
    206    { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
    207    { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
    208    { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
    209    { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
    210    { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
    211    // Ex command
    212    { keys: ':', type: 'ex' }
    213  ];
    214  var defaultKeymapLength = defaultKeymap.length;
    215 
    216  /**
    217   * Ex commands
    218   * Care must be taken when adding to the default Ex command map. For any
    219   * pair of commands that have a shared prefix, at least one of their
    220   * shortNames must not match the prefix of the other command.
    221   */
    222  var defaultExCommandMap = [
    223    { name: 'colorscheme', shortName: 'colo' },
    224    { name: 'map' },
    225    { name: 'imap', shortName: 'im' },
    226    { name: 'nmap', shortName: 'nm' },
    227    { name: 'vmap', shortName: 'vm' },
    228    { name: 'unmap' },
    229    { name: 'write', shortName: 'w' },
    230    { name: 'undo', shortName: 'u' },
    231    { name: 'redo', shortName: 'red' },
    232    { name: 'set', shortName: 'se' },
    233    { name: 'set', shortName: 'se' },
    234    { name: 'setlocal', shortName: 'setl' },
    235    { name: 'setglobal', shortName: 'setg' },
    236    { name: 'sort', shortName: 'sor' },
    237    { name: 'substitute', shortName: 's', possiblyAsync: true },
    238    { name: 'nohlsearch', shortName: 'noh' },
    239    { name: 'yank', shortName: 'y' },
    240    { name: 'delmarks', shortName: 'delm' },
    241    { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true },
    242    { name: 'global', shortName: 'g' }
    243  ];
    244 
    245  var Pos = CodeMirror.Pos;
    246 
    247  var Vim = function() {
    248    function enterVimMode(cm) {
    249      cm.setOption('disableInput', true);
    250      cm.setOption('showCursorWhenSelecting', false);
    251      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
    252      cm.on('cursorActivity', onCursorActivity);
    253      maybeInitVimState(cm);
    254      CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
    255    }
    256 
    257    function leaveVimMode(cm) {
    258      cm.setOption('disableInput', false);
    259      cm.off('cursorActivity', onCursorActivity);
    260      CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
    261      cm.state.vim = null;
    262    }
    263 
    264    function detachVimMap(cm, next) {
    265      if (this == CodeMirror.keyMap.vim) {
    266        CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");
    267        if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
    268          disableFatCursorMark(cm);
    269          cm.getInputField().style.caretColor = "";
    270        }
    271      }
    272 
    273      if (!next || next.attach != attachVimMap)
    274        leaveVimMode(cm);
    275    }
    276    function attachVimMap(cm, prev) {
    277      if (this == CodeMirror.keyMap.vim) {
    278        CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");
    279        if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
    280          enableFatCursorMark(cm);
    281          cm.getInputField().style.caretColor = "transparent";
    282        }
    283      }
    284 
    285      if (!prev || prev.attach != attachVimMap)
    286        enterVimMode(cm);
    287    }
    288 
    289    function updateFatCursorMark(cm) {
    290      if (!cm.state.fatCursorMarks) return;
    291      clearFatCursorMark(cm);
    292      var ranges = cm.listSelections(), result = []
    293      for (var i = 0; i < ranges.length; i++) {
    294        var range = ranges[i]
    295        if (range.empty()) {
    296          if (range.anchor.ch < cm.getLine(range.anchor.line).length) {
    297            result.push(cm.markText(range.anchor, Pos(range.anchor.line, range.anchor.ch + 1),
    298                                    {className: "cm-fat-cursor-mark"}))
    299          } else {
    300            var widget = document.createElement("span")
    301            widget.textContent = "\u00a0"
    302            widget.className = "cm-fat-cursor-mark"
    303            result.push(cm.setBookmark(range.anchor, {widget: widget}))
    304          }
    305        }
    306      }
    307      cm.state.fatCursorMarks = result;
    308    }
    309 
    310    function clearFatCursorMark(cm) {
    311      var marks = cm.state.fatCursorMarks;
    312      if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();
    313    }
    314 
    315    function enableFatCursorMark(cm) {
    316      cm.state.fatCursorMarks = [];
    317      updateFatCursorMark(cm)
    318      cm.on("cursorActivity", updateFatCursorMark)
    319    }
    320 
    321    function disableFatCursorMark(cm) {
    322      clearFatCursorMark(cm);
    323      cm.off("cursorActivity", updateFatCursorMark);
    324      // explicitly set fatCursorMarks to null because event listener above
    325      // can be invoke after removing it, if off is called from operation
    326      cm.state.fatCursorMarks = null;
    327    }
    328 
    329    // Deprecated, simply setting the keymap works again.
    330    CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {
    331      if (val && cm.getOption("keyMap") != "vim")
    332        cm.setOption("keyMap", "vim");
    333      else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap")))
    334        cm.setOption("keyMap", "default");
    335    });
    336 
    337    function cmKey(key, cm) {
    338      if (!cm) { return undefined; }
    339      if (this[key]) { return this[key]; }
    340      var vimKey = cmKeyToVimKey(key);
    341      if (!vimKey) {
    342        return false;
    343      }
    344      var cmd = CodeMirror.Vim.findKey(cm, vimKey);
    345      if (typeof cmd == 'function') {
    346        CodeMirror.signal(cm, 'vim-keypress', vimKey);
    347      }
    348      return cmd;
    349    }
    350 
    351    var modifiers = {'Shift': 'S', 'Ctrl': 'C', 'Alt': 'A', 'Cmd': 'D', 'Mod': 'A'};
    352    var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del',Insert:'Ins'};
    353    function cmKeyToVimKey(key) {
    354      if (key.charAt(0) == '\'') {
    355        // Keypress character binding of format "'a'"
    356        return key.charAt(1);
    357      }
    358      var pieces = key.split(/-(?!$)/);
    359      var lastPiece = pieces[pieces.length - 1];
    360      if (pieces.length == 1 && pieces[0].length == 1) {
    361        // No-modifier bindings use literal character bindings above. Skip.
    362        return false;
    363      } else if (pieces.length == 2 && pieces[0] == 'Shift' && lastPiece.length == 1) {
    364        // Ignore Shift+char bindings as they should be handled by literal character.
    365        return false;
    366      }
    367      var hasCharacter = false;
    368      for (var i = 0; i < pieces.length; i++) {
    369        var piece = pieces[i];
    370        if (piece in modifiers) { pieces[i] = modifiers[piece]; }
    371        else { hasCharacter = true; }
    372        if (piece in specialKeys) { pieces[i] = specialKeys[piece]; }
    373      }
    374      if (!hasCharacter) {
    375        // Vim does not support modifier only keys.
    376        return false;
    377      }
    378      // TODO: Current bindings expect the character to be lower case, but
    379      // it looks like vim key notation uses upper case.
    380      if (isUpperCase(lastPiece)) {
    381        pieces[pieces.length - 1] = lastPiece.toLowerCase();
    382      }
    383      return '<' + pieces.join('-') + '>';
    384    }
    385 
    386    function getOnPasteFn(cm) {
    387      var vim = cm.state.vim;
    388      if (!vim.onPasteFn) {
    389        vim.onPasteFn = function() {
    390          if (!vim.insertMode) {
    391            cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
    392            actions.enterInsertMode(cm, {}, vim);
    393          }
    394        };
    395      }
    396      return vim.onPasteFn;
    397    }
    398 
    399    var numberRegex = /[\d]/;
    400    var wordCharTest = [CodeMirror.isWordChar, function(ch) {
    401      return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch);
    402    }], bigWordCharTest = [function(ch) {
    403      return /\S/.test(ch);
    404    }];
    405    function makeKeyRange(start, size) {
    406      var keys = [];
    407      for (var i = start; i < start + size; i++) {
    408        keys.push(String.fromCharCode(i));
    409      }
    410      return keys;
    411    }
    412    var upperCaseAlphabet = makeKeyRange(65, 26);
    413    var lowerCaseAlphabet = makeKeyRange(97, 26);
    414    var numbers = makeKeyRange(48, 10);
    415    var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
    416    var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':', '/']);
    417 
    418    function isLine(cm, line) {
    419      return line >= cm.firstLine() && line <= cm.lastLine();
    420    }
    421    function isLowerCase(k) {
    422      return (/^[a-z]$/).test(k);
    423    }
    424    function isMatchableSymbol(k) {
    425      return '()[]{}'.indexOf(k) != -1;
    426    }
    427    function isNumber(k) {
    428      return numberRegex.test(k);
    429    }
    430    function isUpperCase(k) {
    431      return (/^[A-Z]$/).test(k);
    432    }
    433    function isWhiteSpaceString(k) {
    434      return (/^\s*$/).test(k);
    435    }
    436    function isEndOfSentenceSymbol(k) {
    437      return '.?!'.indexOf(k) != -1;
    438    }
    439    function inArray(val, arr) {
    440      for (var i = 0; i < arr.length; i++) {
    441        if (arr[i] == val) {
    442          return true;
    443        }
    444      }
    445      return false;
    446    }
    447 
    448    var options = {};
    449    function defineOption(name, defaultValue, type, aliases, callback) {
    450      if (defaultValue === undefined && !callback) {
    451        throw Error('defaultValue is required unless callback is provided');
    452      }
    453      if (!type) { type = 'string'; }
    454      options[name] = {
    455        type: type,
    456        defaultValue: defaultValue,
    457        callback: callback
    458      };
    459      if (aliases) {
    460        for (var i = 0; i < aliases.length; i++) {
    461          options[aliases[i]] = options[name];
    462        }
    463      }
    464      if (defaultValue) {
    465        setOption(name, defaultValue);
    466      }
    467    }
    468 
    469    function setOption(name, value, cm, cfg) {
    470      var option = options[name];
    471      cfg = cfg || {};
    472      var scope = cfg.scope;
    473      if (!option) {
    474        return new Error('Unknown option: ' + name);
    475      }
    476      if (option.type == 'boolean') {
    477        if (value && value !== true) {
    478          return new Error('Invalid argument: ' + name + '=' + value);
    479        } else if (value !== false) {
    480          // Boolean options are set to true if value is not defined.
    481          value = true;
    482        }
    483      }
    484      if (option.callback) {
    485        if (scope !== 'local') {
    486          option.callback(value, undefined);
    487        }
    488        if (scope !== 'global' && cm) {
    489          option.callback(value, cm);
    490        }
    491      } else {
    492        if (scope !== 'local') {
    493          option.value = option.type == 'boolean' ? !!value : value;
    494        }
    495        if (scope !== 'global' && cm) {
    496          cm.state.vim.options[name] = {value: value};
    497        }
    498      }
    499    }
    500 
    501    function getOption(name, cm, cfg) {
    502      var option = options[name];
    503      cfg = cfg || {};
    504      var scope = cfg.scope;
    505      if (!option) {
    506        return new Error('Unknown option: ' + name);
    507      }
    508      if (option.callback) {
    509        var local = cm && option.callback(undefined, cm);
    510        if (scope !== 'global' && local !== undefined) {
    511          return local;
    512        }
    513        if (scope !== 'local') {
    514          return option.callback();
    515        }
    516        return;
    517      } else {
    518        var local = (scope !== 'global') && (cm && cm.state.vim.options[name]);
    519        return (local || (scope !== 'local') && option || {}).value;
    520      }
    521    }
    522 
    523    defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) {
    524      // Option is local. Do nothing for global.
    525      if (cm === undefined) {
    526        return;
    527      }
    528      // The 'filetype' option proxies to the CodeMirror 'mode' option.
    529      if (name === undefined) {
    530        var mode = cm.getOption('mode');
    531        return mode == 'null' ? '' : mode;
    532      } else {
    533        var mode = name == '' ? 'null' : name;
    534        cm.setOption('mode', mode);
    535      }
    536    });
    537 
    538    var createCircularJumpList = function() {
    539      var size = 100;
    540      var pointer = -1;
    541      var head = 0;
    542      var tail = 0;
    543      var buffer = new Array(size);
    544      function add(cm, oldCur, newCur) {
    545        var current = pointer % size;
    546        var curMark = buffer[current];
    547        function useNextSlot(cursor) {
    548          var next = ++pointer % size;
    549          var trashMark = buffer[next];
    550          if (trashMark) {
    551            trashMark.clear();
    552          }
    553          buffer[next] = cm.setBookmark(cursor);
    554        }
    555        if (curMark) {
    556          var markPos = curMark.find();
    557          // avoid recording redundant cursor position
    558          if (markPos && !cursorEqual(markPos, oldCur)) {
    559            useNextSlot(oldCur);
    560          }
    561        } else {
    562          useNextSlot(oldCur);
    563        }
    564        useNextSlot(newCur);
    565        head = pointer;
    566        tail = pointer - size + 1;
    567        if (tail < 0) {
    568          tail = 0;
    569        }
    570      }
    571      function move(cm, offset) {
    572        pointer += offset;
    573        if (pointer > head) {
    574          pointer = head;
    575        } else if (pointer < tail) {
    576          pointer = tail;
    577        }
    578        var mark = buffer[(size + pointer) % size];
    579        // skip marks that are temporarily removed from text buffer
    580        if (mark && !mark.find()) {
    581          var inc = offset > 0 ? 1 : -1;
    582          var newCur;
    583          var oldCur = cm.getCursor();
    584          do {
    585            pointer += inc;
    586            mark = buffer[(size + pointer) % size];
    587            // skip marks that are the same as current position
    588            if (mark &&
    589                (newCur = mark.find()) &&
    590                !cursorEqual(oldCur, newCur)) {
    591              break;
    592            }
    593          } while (pointer < head && pointer > tail);
    594        }
    595        return mark;
    596      }
    597      return {
    598        cachedCursor: undefined, //used for # and * jumps
    599        add: add,
    600        move: move
    601      };
    602    };
    603 
    604    // Returns an object to track the changes associated insert mode.  It
    605    // clones the object that is passed in, or creates an empty object one if
    606    // none is provided.
    607    var createInsertModeChanges = function(c) {
    608      if (c) {
    609        // Copy construction
    610        return {
    611          changes: c.changes,
    612          expectCursorActivityForChange: c.expectCursorActivityForChange
    613        };
    614      }
    615      return {
    616        // Change list
    617        changes: [],
    618        // Set to true on change, false on cursorActivity.
    619        expectCursorActivityForChange: false
    620      };
    621    };
    622 
    623    function MacroModeState() {
    624      this.latestRegister = undefined;
    625      this.isPlaying = false;
    626      this.isRecording = false;
    627      this.replaySearchQueries = [];
    628      this.onRecordingDone = undefined;
    629      this.lastInsertModeChanges = createInsertModeChanges();
    630    }
    631    MacroModeState.prototype = {
    632      exitMacroRecordMode: function() {
    633        var macroModeState = vimGlobalState.macroModeState;
    634        if (macroModeState.onRecordingDone) {
    635          macroModeState.onRecordingDone(); // close dialog
    636        }
    637        macroModeState.onRecordingDone = undefined;
    638        macroModeState.isRecording = false;
    639      },
    640      enterMacroRecordMode: function(cm, registerName) {
    641        var register =
    642            vimGlobalState.registerController.getRegister(registerName);
    643        if (register) {
    644          register.clear();
    645          this.latestRegister = registerName;
    646          if (cm.openDialog) {
    647            this.onRecordingDone = cm.openDialog(
    648                '(recording)['+registerName+']', null, {bottom:true});
    649          }
    650          this.isRecording = true;
    651        }
    652      }
    653    };
    654 
    655    function maybeInitVimState(cm) {
    656      if (!cm.state.vim) {
    657        // Store instance state in the CodeMirror object.
    658        cm.state.vim = {
    659          inputState: new InputState(),
    660          // Vim's input state that triggered the last edit, used to repeat
    661          // motions and operators with '.'.
    662          lastEditInputState: undefined,
    663          // Vim's action command before the last edit, used to repeat actions
    664          // with '.' and insert mode repeat.
    665          lastEditActionCommand: undefined,
    666          // When using jk for navigation, if you move from a longer line to a
    667          // shorter line, the cursor may clip to the end of the shorter line.
    668          // If j is pressed again and cursor goes to the next line, the
    669          // cursor should go back to its horizontal position on the longer
    670          // line if it can. This is to keep track of the horizontal position.
    671          lastHPos: -1,
    672          // Doing the same with screen-position for gj/gk
    673          lastHSPos: -1,
    674          // The last motion command run. Cleared if a non-motion command gets
    675          // executed in between.
    676          lastMotion: null,
    677          marks: {},
    678          // Mark for rendering fake cursor for visual mode.
    679          fakeCursor: null,
    680          insertMode: false,
    681          // Repeat count for changes made in insert mode, triggered by key
    682          // sequences like 3,i. Only exists when insertMode is true.
    683          insertModeRepeat: undefined,
    684          visualMode: false,
    685          // If we are in visual line mode. No effect if visualMode is false.
    686          visualLine: false,
    687          visualBlock: false,
    688          lastSelection: null,
    689          lastPastedText: null,
    690          sel: {},
    691          // Buffer-local/window-local values of vim options.
    692          options: {}
    693        };
    694      }
    695      return cm.state.vim;
    696    }
    697    var vimGlobalState;
    698    function resetVimGlobalState() {
    699      vimGlobalState = {
    700        // The current search query.
    701        searchQuery: null,
    702        // Whether we are searching backwards.
    703        searchIsReversed: false,
    704        // Replace part of the last substituted pattern
    705        lastSubstituteReplacePart: undefined,
    706        jumpList: createCircularJumpList(),
    707        macroModeState: new MacroModeState,
    708        // Recording latest f, t, F or T motion command.
    709        lastCharacterSearch: {increment:0, forward:true, selectedCharacter:''},
    710        registerController: new RegisterController({}),
    711        // search history buffer
    712        searchHistoryController: new HistoryController(),
    713        // ex Command history buffer
    714        exCommandHistoryController : new HistoryController()
    715      };
    716      for (var optionName in options) {
    717        var option = options[optionName];
    718        option.value = option.defaultValue;
    719      }
    720    }
    721 
    722    var lastInsertModeKeyTimer;
    723    var vimApi= {
    724      buildKeyMap: function() {
    725        // TODO: Convert keymap into dictionary format for fast lookup.
    726      },
    727      // Testing hook, though it might be useful to expose the register
    728      // controller anyways.
    729      getRegisterController: function() {
    730        return vimGlobalState.registerController;
    731      },
    732      // Testing hook.
    733      resetVimGlobalState_: resetVimGlobalState,
    734 
    735      // Testing hook.
    736      getVimGlobalState_: function() {
    737        return vimGlobalState;
    738      },
    739 
    740      // Testing hook.
    741      maybeInitVimState_: maybeInitVimState,
    742 
    743      suppressErrorLogging: false,
    744 
    745      InsertModeKey: InsertModeKey,
    746      map: function(lhs, rhs, ctx) {
    747        // Add user defined key bindings.
    748        exCommandDispatcher.map(lhs, rhs, ctx);
    749      },
    750      unmap: function(lhs, ctx) {
    751        exCommandDispatcher.unmap(lhs, ctx);
    752      },
    753      // Non-recursive map function.
    754      // NOTE: This will not create mappings to key maps that aren't present
    755      // in the default key map. See TODO at bottom of function.
    756      noremap: function(lhs, rhs, ctx) {
    757        function toCtxArray(ctx) {
    758          return ctx ? [ctx] : ['normal', 'insert', 'visual'];
    759        }
    760        var ctxsToMap = toCtxArray(ctx);
    761        // Look through all actual defaults to find a map candidate.
    762        var actualLength = defaultKeymap.length, origLength = defaultKeymapLength;
    763        for (var i = actualLength - origLength;
    764             i < actualLength && ctxsToMap.length;
    765             i++) {
    766          var mapping = defaultKeymap[i];
    767          // Omit mappings that operate in the wrong context(s) and those of invalid type.
    768          if (mapping.keys == rhs &&
    769              (!ctx || !mapping.context || mapping.context === ctx) &&
    770              mapping.type.substr(0, 2) !== 'ex' &&
    771              mapping.type.substr(0, 3) !== 'key') {
    772            // Make a shallow copy of the original keymap entry.
    773            var newMapping = {};
    774            for (var key in mapping) {
    775              newMapping[key] = mapping[key];
    776            }
    777            // Modify it point to the new mapping with the proper context.
    778            newMapping.keys = lhs;
    779            if (ctx && !newMapping.context) {
    780              newMapping.context = ctx;
    781            }
    782            // Add it to the keymap with a higher priority than the original.
    783            this._mapCommand(newMapping);
    784            // Record the mapped contexts as complete.
    785            var mappedCtxs = toCtxArray(mapping.context);
    786            ctxsToMap = ctxsToMap.filter(function(el) { return mappedCtxs.indexOf(el) === -1; });
    787          }
    788        }
    789        // TODO: Create non-recursive keyToKey mappings for the unmapped contexts once those exist.
    790      },
    791      // Remove all user-defined mappings for the provided context.
    792      mapclear: function(ctx) {
    793        // Partition the existing keymap into user-defined and true defaults.
    794        var actualLength = defaultKeymap.length,
    795            origLength = defaultKeymapLength;
    796        var userKeymap = defaultKeymap.slice(0, actualLength - origLength);
    797        defaultKeymap = defaultKeymap.slice(actualLength - origLength);
    798        if (ctx) {
    799          // If a specific context is being cleared, we need to keep mappings
    800          // from all other contexts.
    801          for (var i = userKeymap.length - 1; i >= 0; i--) {
    802            var mapping = userKeymap[i];
    803            if (ctx !== mapping.context) {
    804              if (mapping.context) {
    805                this._mapCommand(mapping);
    806              } else {
    807                // `mapping` applies to all contexts so create keymap copies
    808                // for each context except the one being cleared.
    809                var contexts = ['normal', 'insert', 'visual'];
    810                for (var j in contexts) {
    811                  if (contexts[j] !== ctx) {
    812                    var newMapping = {};
    813                    for (var key in mapping) {
    814                      newMapping[key] = mapping[key];
    815                    }
    816                    newMapping.context = contexts[j];
    817                    this._mapCommand(newMapping);
    818                  }
    819                }
    820              }
    821            }
    822          }
    823        }
    824      },
    825      // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace
    826      // them, or somehow make them work with the existing CodeMirror setOption/getOption API.
    827      setOption: setOption,
    828      getOption: getOption,
    829      defineOption: defineOption,
    830      defineEx: function(name, prefix, func){
    831        if (!prefix) {
    832          prefix = name;
    833        } else if (name.indexOf(prefix) !== 0) {
    834          throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
    835        }
    836        exCommands[name]=func;
    837        exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
    838      },
    839      handleKey: function (cm, key, origin) {
    840        var command = this.findKey(cm, key, origin);
    841        if (typeof command === 'function') {
    842          return command();
    843        }
    844      },
    845      /**
    846       * This is the outermost function called by CodeMirror, after keys have
    847       * been mapped to their Vim equivalents.
    848       *
    849       * Finds a command based on the key (and cached keys if there is a
    850       * multi-key sequence). Returns `undefined` if no key is matched, a noop
    851       * function if a partial match is found (multi-key), and a function to
    852       * execute the bound command if a a key is matched. The function always
    853       * returns true.
    854       */
    855      findKey: function(cm, key, origin) {
    856        var vim = maybeInitVimState(cm);
    857        function handleMacroRecording() {
    858          var macroModeState = vimGlobalState.macroModeState;
    859          if (macroModeState.isRecording) {
    860            if (key == 'q') {
    861              macroModeState.exitMacroRecordMode();
    862              clearInputState(cm);
    863              return true;
    864            }
    865            if (origin != 'mapping') {
    866              logKey(macroModeState, key);
    867            }
    868          }
    869        }
    870        function handleEsc() {
    871          if (key == '<Esc>') {
    872            // Clear input state and get back to normal mode.
    873            clearInputState(cm);
    874            if (vim.visualMode) {
    875              exitVisualMode(cm);
    876            } else if (vim.insertMode) {
    877              exitInsertMode(cm);
    878            }
    879            return true;
    880          }
    881        }
    882        function doKeyToKey(keys) {
    883          // TODO: prevent infinite recursion.
    884          var match;
    885          while (keys) {
    886            // Pull off one command key, which is either a single character
    887            // or a special sequence wrapped in '<' and '>', e.g. '<Space>'.
    888            match = (/<\w+-.+?>|<\w+>|./).exec(keys);
    889            key = match[0];
    890            keys = keys.substring(match.index + key.length);
    891            CodeMirror.Vim.handleKey(cm, key, 'mapping');
    892          }
    893        }
    894 
    895        function handleKeyInsertMode() {
    896          if (handleEsc()) { return true; }
    897          var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
    898          var keysAreChars = key.length == 1;
    899          var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
    900          // Need to check all key substrings in insert mode.
    901          while (keys.length > 1 && match.type != 'full') {
    902            var keys = vim.inputState.keyBuffer = keys.slice(1);
    903            var thisMatch = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
    904            if (thisMatch.type != 'none') { match = thisMatch; }
    905          }
    906          if (match.type == 'none') { clearInputState(cm); return false; }
    907          else if (match.type == 'partial') {
    908            if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
    909            lastInsertModeKeyTimer = window.setTimeout(
    910              function() { if (vim.insertMode && vim.inputState.keyBuffer) { clearInputState(cm); } },
    911              getOption('insertModeEscKeysTimeout'));
    912            return !keysAreChars;
    913          }
    914 
    915          if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
    916          if (keysAreChars) {
    917            var selections = cm.listSelections();
    918            for (var i = 0; i < selections.length; i++) {
    919              var here = selections[i].head;
    920              cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input');
    921            }
    922            vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop();
    923          }
    924          clearInputState(cm);
    925          return match.command;
    926        }
    927 
    928        function handleKeyNonInsertMode() {
    929          if (handleMacroRecording() || handleEsc()) { return true; }
    930 
    931          var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
    932          if (/^[1-9]\d*$/.test(keys)) { return true; }
    933 
    934          var keysMatcher = /^(\d*)(.*)$/.exec(keys);
    935          if (!keysMatcher) { clearInputState(cm); return false; }
    936          var context = vim.visualMode ? 'visual' :
    937                                         'normal';
    938          var match = commandDispatcher.matchCommand(keysMatcher[2] || keysMatcher[1], defaultKeymap, vim.inputState, context);
    939          if (match.type == 'none') { clearInputState(cm); return false; }
    940          else if (match.type == 'partial') { return true; }
    941 
    942          vim.inputState.keyBuffer = '';
    943          var keysMatcher = /^(\d*)(.*)$/.exec(keys);
    944          if (keysMatcher[1] && keysMatcher[1] != '0') {
    945            vim.inputState.pushRepeatDigit(keysMatcher[1]);
    946          }
    947          return match.command;
    948        }
    949 
    950        var command;
    951        if (vim.insertMode) { command = handleKeyInsertMode(); }
    952        else { command = handleKeyNonInsertMode(); }
    953        if (command === false) {
    954          return !vim.insertMode && key.length === 1 ? function() { return true; } : undefined;
    955        } else if (command === true) {
    956          // TODO: Look into using CodeMirror's multi-key handling.
    957          // Return no-op since we are caching the key. Counts as handled, but
    958          // don't want act on it just yet.
    959          return function() { return true; };
    960        } else {
    961          return function() {
    962            return cm.operation(function() {
    963              cm.curOp.isVimOp = true;
    964              try {
    965                if (command.type == 'keyToKey') {
    966                  doKeyToKey(command.toKeys);
    967                } else {
    968                  commandDispatcher.processCommand(cm, vim, command);
    969                }
    970              } catch (e) {
    971                // clear VIM state in case it's in a bad state.
    972                cm.state.vim = undefined;
    973                maybeInitVimState(cm);
    974                if (!CodeMirror.Vim.suppressErrorLogging) {
    975                  console['log'](e);
    976                }
    977                throw e;
    978              }
    979              return true;
    980            });
    981          };
    982        }
    983      },
    984      handleEx: function(cm, input) {
    985        exCommandDispatcher.processCommand(cm, input);
    986      },
    987 
    988      defineMotion: defineMotion,
    989      defineAction: defineAction,
    990      defineOperator: defineOperator,
    991      mapCommand: mapCommand,
    992      _mapCommand: _mapCommand,
    993 
    994      defineRegister: defineRegister,
    995 
    996      exitVisualMode: exitVisualMode,
    997      exitInsertMode: exitInsertMode
    998    };
    999 
   1000    // Represents the current input state.
   1001    function InputState() {
   1002      this.prefixRepeat = [];
   1003      this.motionRepeat = [];
   1004 
   1005      this.operator = null;
   1006      this.operatorArgs = null;
   1007      this.motion = null;
   1008      this.motionArgs = null;
   1009      this.keyBuffer = []; // For matching multi-key commands.
   1010      this.registerName = null; // Defaults to the unnamed register.
   1011    }
   1012    InputState.prototype.pushRepeatDigit = function(n) {
   1013      if (!this.operator) {
   1014        this.prefixRepeat = this.prefixRepeat.concat(n);
   1015      } else {
   1016        this.motionRepeat = this.motionRepeat.concat(n);
   1017      }
   1018    };
   1019    InputState.prototype.getRepeat = function() {
   1020      var repeat = 0;
   1021      if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {
   1022        repeat = 1;
   1023        if (this.prefixRepeat.length > 0) {
   1024          repeat *= parseInt(this.prefixRepeat.join(''), 10);
   1025        }
   1026        if (this.motionRepeat.length > 0) {
   1027          repeat *= parseInt(this.motionRepeat.join(''), 10);
   1028        }
   1029      }
   1030      return repeat;
   1031    };
   1032 
   1033    function clearInputState(cm, reason) {
   1034      cm.state.vim.inputState = new InputState();
   1035      CodeMirror.signal(cm, 'vim-command-done', reason);
   1036    }
   1037 
   1038    /*
   1039     * Register stores information about copy and paste registers.  Besides
   1040     * text, a register must store whether it is linewise (i.e., when it is
   1041     * pasted, should it insert itself into a new line, or should the text be
   1042     * inserted at the cursor position.)
   1043     */
   1044    function Register(text, linewise, blockwise) {
   1045      this.clear();
   1046      this.keyBuffer = [text || ''];
   1047      this.insertModeChanges = [];
   1048      this.searchQueries = [];
   1049      this.linewise = !!linewise;
   1050      this.blockwise = !!blockwise;
   1051    }
   1052    Register.prototype = {
   1053      setText: function(text, linewise, blockwise) {
   1054        this.keyBuffer = [text || ''];
   1055        this.linewise = !!linewise;
   1056        this.blockwise = !!blockwise;
   1057      },
   1058      pushText: function(text, linewise) {
   1059        // if this register has ever been set to linewise, use linewise.
   1060        if (linewise) {
   1061          if (!this.linewise) {
   1062            this.keyBuffer.push('\n');
   1063          }
   1064          this.linewise = true;
   1065        }
   1066        this.keyBuffer.push(text);
   1067      },
   1068      pushInsertModeChanges: function(changes) {
   1069        this.insertModeChanges.push(createInsertModeChanges(changes));
   1070      },
   1071      pushSearchQuery: function(query) {
   1072        this.searchQueries.push(query);
   1073      },
   1074      clear: function() {
   1075        this.keyBuffer = [];
   1076        this.insertModeChanges = [];
   1077        this.searchQueries = [];
   1078        this.linewise = false;
   1079      },
   1080      toString: function() {
   1081        return this.keyBuffer.join('');
   1082      }
   1083    };
   1084 
   1085    /**
   1086     * Defines an external register.
   1087     *
   1088     * The name should be a single character that will be used to reference the register.
   1089     * The register should support setText, pushText, clear, and toString(). See Register
   1090     * for a reference implementation.
   1091     */
   1092    function defineRegister(name, register) {
   1093      var registers = vimGlobalState.registerController.registers;
   1094      if (!name || name.length != 1) {
   1095        throw Error('Register name must be 1 character');
   1096      }
   1097      if (registers[name]) {
   1098        throw Error('Register already defined ' + name);
   1099      }
   1100      registers[name] = register;
   1101      validRegisters.push(name);
   1102    }
   1103 
   1104    /*
   1105     * vim registers allow you to keep many independent copy and paste buffers.
   1106     * See http://usevim.com/2012/04/13/registers/ for an introduction.
   1107     *
   1108     * RegisterController keeps the state of all the registers.  An initial
   1109     * state may be passed in.  The unnamed register '"' will always be
   1110     * overridden.
   1111     */
   1112    function RegisterController(registers) {
   1113      this.registers = registers;
   1114      this.unnamedRegister = registers['"'] = new Register();
   1115      registers['.'] = new Register();
   1116      registers[':'] = new Register();
   1117      registers['/'] = new Register();
   1118    }
   1119    RegisterController.prototype = {
   1120      pushText: function(registerName, operator, text, linewise, blockwise) {
   1121        if (linewise && text.charAt(text.length - 1) !== '\n'){
   1122          text += '\n';
   1123        }
   1124        // Lowercase and uppercase registers refer to the same register.
   1125        // Uppercase just means append.
   1126        var register = this.isValidRegister(registerName) ?
   1127            this.getRegister(registerName) : null;
   1128        // if no register/an invalid register was specified, things go to the
   1129        // default registers
   1130        if (!register) {
   1131          switch (operator) {
   1132            case 'yank':
   1133              // The 0 register contains the text from the most recent yank.
   1134              this.registers['0'] = new Register(text, linewise, blockwise);
   1135              break;
   1136            case 'delete':
   1137            case 'change':
   1138              if (text.indexOf('\n') == -1) {
   1139                // Delete less than 1 line. Update the small delete register.
   1140                this.registers['-'] = new Register(text, linewise);
   1141              } else {
   1142                // Shift down the contents of the numbered registers and put the
   1143                // deleted text into register 1.
   1144                this.shiftNumericRegisters_();
   1145                this.registers['1'] = new Register(text, linewise);
   1146              }
   1147              break;
   1148          }
   1149          // Make sure the unnamed register is set to what just happened
   1150          this.unnamedRegister.setText(text, linewise, blockwise);
   1151          return;
   1152        }
   1153 
   1154        // If we've gotten to this point, we've actually specified a register
   1155        var append = isUpperCase(registerName);
   1156        if (append) {
   1157          register.pushText(text, linewise);
   1158        } else {
   1159          register.setText(text, linewise, blockwise);
   1160        }
   1161        // The unnamed register always has the same value as the last used
   1162        // register.
   1163        this.unnamedRegister.setText(register.toString(), linewise);
   1164      },
   1165      // Gets the register named @name.  If one of @name doesn't already exist,
   1166      // create it.  If @name is invalid, return the unnamedRegister.
   1167      getRegister: function(name) {
   1168        if (!this.isValidRegister(name)) {
   1169          return this.unnamedRegister;
   1170        }
   1171        name = name.toLowerCase();
   1172        if (!this.registers[name]) {
   1173          this.registers[name] = new Register();
   1174        }
   1175        return this.registers[name];
   1176      },
   1177      isValidRegister: function(name) {
   1178        return name && inArray(name, validRegisters);
   1179      },
   1180      shiftNumericRegisters_: function() {
   1181        for (var i = 9; i >= 2; i--) {
   1182          this.registers[i] = this.getRegister('' + (i - 1));
   1183        }
   1184      }
   1185    };
   1186    function HistoryController() {
   1187        this.historyBuffer = [];
   1188        this.iterator = 0;
   1189        this.initialPrefix = null;
   1190    }
   1191    HistoryController.prototype = {
   1192      // the input argument here acts a user entered prefix for a small time
   1193      // until we start autocompletion in which case it is the autocompleted.
   1194      nextMatch: function (input, up) {
   1195        var historyBuffer = this.historyBuffer;
   1196        var dir = up ? -1 : 1;
   1197        if (this.initialPrefix === null) this.initialPrefix = input;
   1198        for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i+= dir) {
   1199          var element = historyBuffer[i];
   1200          for (var j = 0; j <= element.length; j++) {
   1201            if (this.initialPrefix == element.substring(0, j)) {
   1202              this.iterator = i;
   1203              return element;
   1204            }
   1205          }
   1206        }
   1207        // should return the user input in case we reach the end of buffer.
   1208        if (i >= historyBuffer.length) {
   1209          this.iterator = historyBuffer.length;
   1210          return this.initialPrefix;
   1211        }
   1212        // return the last autocompleted query or exCommand as it is.
   1213        if (i < 0 ) return input;
   1214      },
   1215      pushInput: function(input) {
   1216        var index = this.historyBuffer.indexOf(input);
   1217        if (index > -1) this.historyBuffer.splice(index, 1);
   1218        if (input.length) this.historyBuffer.push(input);
   1219      },
   1220      reset: function() {
   1221        this.initialPrefix = null;
   1222        this.iterator = this.historyBuffer.length;
   1223      }
   1224    };
   1225    var commandDispatcher = {
   1226      matchCommand: function(keys, keyMap, inputState, context) {
   1227        var matches = commandMatches(keys, keyMap, context, inputState);
   1228        if (!matches.full && !matches.partial) {
   1229          return {type: 'none'};
   1230        } else if (!matches.full && matches.partial) {
   1231          return {type: 'partial'};
   1232        }
   1233 
   1234        var bestMatch;
   1235        for (var i = 0; i < matches.full.length; i++) {
   1236          var match = matches.full[i];
   1237          if (!bestMatch) {
   1238            bestMatch = match;
   1239          }
   1240        }
   1241        if (bestMatch.keys.slice(-11) == '<character>') {
   1242          var character = lastChar(keys);
   1243          if (!character) return {type: 'none'};
   1244          inputState.selectedCharacter = character;
   1245        }
   1246        return {type: 'full', command: bestMatch};
   1247      },
   1248      processCommand: function(cm, vim, command) {
   1249        vim.inputState.repeatOverride = command.repeatOverride;
   1250        switch (command.type) {
   1251          case 'motion':
   1252            this.processMotion(cm, vim, command);
   1253            break;
   1254          case 'operator':
   1255            this.processOperator(cm, vim, command);
   1256            break;
   1257          case 'operatorMotion':
   1258            this.processOperatorMotion(cm, vim, command);
   1259            break;
   1260          case 'action':
   1261            this.processAction(cm, vim, command);
   1262            break;
   1263          case 'search':
   1264            this.processSearch(cm, vim, command);
   1265            break;
   1266          case 'ex':
   1267          case 'keyToEx':
   1268            this.processEx(cm, vim, command);
   1269            break;
   1270          default:
   1271            break;
   1272        }
   1273      },
   1274      processMotion: function(cm, vim, command) {
   1275        vim.inputState.motion = command.motion;
   1276        vim.inputState.motionArgs = copyArgs(command.motionArgs);
   1277        this.evalInput(cm, vim);
   1278      },
   1279      processOperator: function(cm, vim, command) {
   1280        var inputState = vim.inputState;
   1281        if (inputState.operator) {
   1282          if (inputState.operator == command.operator) {
   1283            // Typing an operator twice like 'dd' makes the operator operate
   1284            // linewise
   1285            inputState.motion = 'expandToLine';
   1286            inputState.motionArgs = { linewise: true };
   1287            this.evalInput(cm, vim);
   1288            return;
   1289          } else {
   1290            // 2 different operators in a row doesn't make sense.
   1291            clearInputState(cm);
   1292          }
   1293        }
   1294        inputState.operator = command.operator;
   1295        inputState.operatorArgs = copyArgs(command.operatorArgs);
   1296        if (vim.visualMode) {
   1297          // Operating on a selection in visual mode. We don't need a motion.
   1298          this.evalInput(cm, vim);
   1299        }
   1300      },
   1301      processOperatorMotion: function(cm, vim, command) {
   1302        var visualMode = vim.visualMode;
   1303        var operatorMotionArgs = copyArgs(command.operatorMotionArgs);
   1304        if (operatorMotionArgs) {
   1305          // Operator motions may have special behavior in visual mode.
   1306          if (visualMode && operatorMotionArgs.visualLine) {
   1307            vim.visualLine = true;
   1308          }
   1309        }
   1310        this.processOperator(cm, vim, command);
   1311        if (!visualMode) {
   1312          this.processMotion(cm, vim, command);
   1313        }
   1314      },
   1315      processAction: function(cm, vim, command) {
   1316        var inputState = vim.inputState;
   1317        var repeat = inputState.getRepeat();
   1318        var repeatIsExplicit = !!repeat;
   1319        var actionArgs = copyArgs(command.actionArgs) || {};
   1320        if (inputState.selectedCharacter) {
   1321          actionArgs.selectedCharacter = inputState.selectedCharacter;
   1322        }
   1323        // Actions may or may not have motions and operators. Do these first.
   1324        if (command.operator) {
   1325          this.processOperator(cm, vim, command);
   1326        }
   1327        if (command.motion) {
   1328          this.processMotion(cm, vim, command);
   1329        }
   1330        if (command.motion || command.operator) {
   1331          this.evalInput(cm, vim);
   1332        }
   1333        actionArgs.repeat = repeat || 1;
   1334        actionArgs.repeatIsExplicit = repeatIsExplicit;
   1335        actionArgs.registerName = inputState.registerName;
   1336        clearInputState(cm);
   1337        vim.lastMotion = null;
   1338        if (command.isEdit) {
   1339          this.recordLastEdit(vim, inputState, command);
   1340        }
   1341        actions[command.action](cm, actionArgs, vim);
   1342      },
   1343      processSearch: function(cm, vim, command) {
   1344        if (!cm.getSearchCursor) {
   1345          // Search depends on SearchCursor.
   1346          return;
   1347        }
   1348        var forward = command.searchArgs.forward;
   1349        var wholeWordOnly = command.searchArgs.wholeWordOnly;
   1350        getSearchState(cm).setReversed(!forward);
   1351        var promptPrefix = (forward) ? '/' : '?';
   1352        var originalQuery = getSearchState(cm).getQuery();
   1353        var originalScrollPos = cm.getScrollInfo();
   1354        function handleQuery(query, ignoreCase, smartCase) {
   1355          vimGlobalState.searchHistoryController.pushInput(query);
   1356          vimGlobalState.searchHistoryController.reset();
   1357          try {
   1358            updateSearchQuery(cm, query, ignoreCase, smartCase);
   1359          } catch (e) {
   1360            showConfirm(cm, 'Invalid regex: ' + query);
   1361            clearInputState(cm);
   1362            return;
   1363          }
   1364          commandDispatcher.processMotion(cm, vim, {
   1365            type: 'motion',
   1366            motion: 'findNext',
   1367            motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }
   1368          });
   1369        }
   1370        function onPromptClose(query) {
   1371          cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
   1372          handleQuery(query, true /** ignoreCase */, true /** smartCase */);
   1373          var macroModeState = vimGlobalState.macroModeState;
   1374          if (macroModeState.isRecording) {
   1375            logSearchQuery(macroModeState, query);
   1376          }
   1377        }
   1378        function onPromptKeyUp(e, query, close) {
   1379          var keyName = CodeMirror.keyName(e), up, offset;
   1380          if (keyName == 'Up' || keyName == 'Down') {
   1381            up = keyName == 'Up' ? true : false;
   1382            offset = e.target ? e.target.selectionEnd : 0;
   1383            query = vimGlobalState.searchHistoryController.nextMatch(query, up) || '';
   1384            close(query);
   1385            if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length);
   1386          } else {
   1387            if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')
   1388              vimGlobalState.searchHistoryController.reset();
   1389          }
   1390          var parsedQuery;
   1391          try {
   1392            parsedQuery = updateSearchQuery(cm, query,
   1393                true /** ignoreCase */, true /** smartCase */);
   1394          } catch (e) {
   1395            // Swallow bad regexes for incremental search.
   1396          }
   1397          if (parsedQuery) {
   1398            cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);
   1399          } else {
   1400            clearSearchHighlight(cm);
   1401            cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
   1402          }
   1403        }
   1404        function onPromptKeyDown(e, query, close) {
   1405          var keyName = CodeMirror.keyName(e);
   1406          if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||
   1407              (keyName == 'Backspace' && query == '')) {
   1408            vimGlobalState.searchHistoryController.pushInput(query);
   1409            vimGlobalState.searchHistoryController.reset();
   1410            updateSearchQuery(cm, originalQuery);
   1411            clearSearchHighlight(cm);
   1412            cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
   1413            CodeMirror.e_stop(e);
   1414            clearInputState(cm);
   1415            close();
   1416            cm.focus();
   1417          } else if (keyName == 'Up' || keyName == 'Down') {
   1418            CodeMirror.e_stop(e);
   1419          } else if (keyName == 'Ctrl-U') {
   1420            // Ctrl-U clears input.
   1421            CodeMirror.e_stop(e);
   1422            close('');
   1423          }
   1424        }
   1425        switch (command.searchArgs.querySrc) {
   1426          case 'prompt':
   1427            var macroModeState = vimGlobalState.macroModeState;
   1428            if (macroModeState.isPlaying) {
   1429              var query = macroModeState.replaySearchQueries.shift();
   1430              handleQuery(query, true /** ignoreCase */, false /** smartCase */);
   1431            } else {
   1432              showPrompt(cm, {
   1433                  onClose: onPromptClose,
   1434                  prefix: promptPrefix,
   1435                  desc: searchPromptDesc,
   1436                  onKeyUp: onPromptKeyUp,
   1437                  onKeyDown: onPromptKeyDown
   1438              });
   1439            }
   1440            break;
   1441          case 'wordUnderCursor':
   1442            var word = expandWordUnderCursor(cm, false /** inclusive */,
   1443                true /** forward */, false /** bigWord */,
   1444                true /** noSymbol */);
   1445            var isKeyword = true;
   1446            if (!word) {
   1447              word = expandWordUnderCursor(cm, false /** inclusive */,
   1448                  true /** forward */, false /** bigWord */,
   1449                  false /** noSymbol */);
   1450              isKeyword = false;
   1451            }
   1452            if (!word) {
   1453              return;
   1454            }
   1455            var query = cm.getLine(word.start.line).substring(word.start.ch,
   1456                word.end.ch);
   1457            if (isKeyword && wholeWordOnly) {
   1458                query = '\\b' + query + '\\b';
   1459            } else {
   1460              query = escapeRegex(query);
   1461            }
   1462 
   1463            // cachedCursor is used to save the old position of the cursor
   1464            // when * or # causes vim to seek for the nearest word and shift
   1465            // the cursor before entering the motion.
   1466            vimGlobalState.jumpList.cachedCursor = cm.getCursor();
   1467            cm.setCursor(word.start);
   1468 
   1469            handleQuery(query, true /** ignoreCase */, false /** smartCase */);
   1470            break;
   1471        }
   1472      },
   1473      processEx: function(cm, vim, command) {
   1474        function onPromptClose(input) {
   1475          // Give the prompt some time to close so that if processCommand shows
   1476          // an error, the elements don't overlap.
   1477          vimGlobalState.exCommandHistoryController.pushInput(input);
   1478          vimGlobalState.exCommandHistoryController.reset();
   1479          exCommandDispatcher.processCommand(cm, input);
   1480        }
   1481        function onPromptKeyDown(e, input, close) {
   1482          var keyName = CodeMirror.keyName(e), up, offset;
   1483          if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||
   1484              (keyName == 'Backspace' && input == '')) {
   1485            vimGlobalState.exCommandHistoryController.pushInput(input);
   1486            vimGlobalState.exCommandHistoryController.reset();
   1487            CodeMirror.e_stop(e);
   1488            clearInputState(cm);
   1489            close();
   1490            cm.focus();
   1491          }
   1492          if (keyName == 'Up' || keyName == 'Down') {
   1493            CodeMirror.e_stop(e);
   1494            up = keyName == 'Up' ? true : false;
   1495            offset = e.target ? e.target.selectionEnd : 0;
   1496            input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || '';
   1497            close(input);
   1498            if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length);
   1499          } else if (keyName == 'Ctrl-U') {
   1500            // Ctrl-U clears input.
   1501            CodeMirror.e_stop(e);
   1502            close('');
   1503          } else {
   1504            if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')
   1505              vimGlobalState.exCommandHistoryController.reset();
   1506          }
   1507        }
   1508        if (command.type == 'keyToEx') {
   1509          // Handle user defined Ex to Ex mappings
   1510          exCommandDispatcher.processCommand(cm, command.exArgs.input);
   1511        } else {
   1512          if (vim.visualMode) {
   1513            showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
   1514                onKeyDown: onPromptKeyDown, selectValueOnOpen: false});
   1515          } else {
   1516            showPrompt(cm, { onClose: onPromptClose, prefix: ':',
   1517                onKeyDown: onPromptKeyDown});
   1518          }
   1519        }
   1520      },
   1521      evalInput: function(cm, vim) {
   1522        // If the motion command is set, execute both the operator and motion.
   1523        // Otherwise return.
   1524        var inputState = vim.inputState;
   1525        var motion = inputState.motion;
   1526        var motionArgs = inputState.motionArgs || {};
   1527        var operator = inputState.operator;
   1528        var operatorArgs = inputState.operatorArgs || {};
   1529        var registerName = inputState.registerName;
   1530        var sel = vim.sel;
   1531        // TODO: Make sure cm and vim selections are identical outside visual mode.
   1532        var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head'));
   1533        var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor'));
   1534        var oldHead = copyCursor(origHead);
   1535        var oldAnchor = copyCursor(origAnchor);
   1536        var newHead, newAnchor;
   1537        var repeat;
   1538        if (operator) {
   1539          this.recordLastEdit(vim, inputState);
   1540        }
   1541        if (inputState.repeatOverride !== undefined) {
   1542          // If repeatOverride is specified, that takes precedence over the
   1543          // input state's repeat. Used by Ex mode and can be user defined.
   1544          repeat = inputState.repeatOverride;
   1545        } else {
   1546          repeat = inputState.getRepeat();
   1547        }
   1548        if (repeat > 0 && motionArgs.explicitRepeat) {
   1549          motionArgs.repeatIsExplicit = true;
   1550        } else if (motionArgs.noRepeat ||
   1551            (!motionArgs.explicitRepeat && repeat === 0)) {
   1552          repeat = 1;
   1553          motionArgs.repeatIsExplicit = false;
   1554        }
   1555        if (inputState.selectedCharacter) {
   1556          // If there is a character input, stick it in all of the arg arrays.
   1557          motionArgs.selectedCharacter = operatorArgs.selectedCharacter =
   1558              inputState.selectedCharacter;
   1559        }
   1560        motionArgs.repeat = repeat;
   1561        clearInputState(cm);
   1562        if (motion) {
   1563          var motionResult = motions[motion](cm, origHead, motionArgs, vim);
   1564          vim.lastMotion = motions[motion];
   1565          if (!motionResult) {
   1566            return;
   1567          }
   1568          if (motionArgs.toJumplist) {
   1569            var jumpList = vimGlobalState.jumpList;
   1570            // if the current motion is # or *, use cachedCursor
   1571            var cachedCursor = jumpList.cachedCursor;
   1572            if (cachedCursor) {
   1573              recordJumpPosition(cm, cachedCursor, motionResult);
   1574              delete jumpList.cachedCursor;
   1575            } else {
   1576              recordJumpPosition(cm, origHead, motionResult);
   1577            }
   1578          }
   1579          if (motionResult instanceof Array) {
   1580            newAnchor = motionResult[0];
   1581            newHead = motionResult[1];
   1582          } else {
   1583            newHead = motionResult;
   1584          }
   1585          // TODO: Handle null returns from motion commands better.
   1586          if (!newHead) {
   1587            newHead = copyCursor(origHead);
   1588          }
   1589          if (vim.visualMode) {
   1590            if (!(vim.visualBlock && newHead.ch === Infinity)) {
   1591              newHead = clipCursorToContent(cm, newHead, vim.visualBlock);
   1592            }
   1593            if (newAnchor) {
   1594              newAnchor = clipCursorToContent(cm, newAnchor, true);
   1595            }
   1596            newAnchor = newAnchor || oldAnchor;
   1597            sel.anchor = newAnchor;
   1598            sel.head = newHead;
   1599            updateCmSelection(cm);
   1600            updateMark(cm, vim, '<',
   1601                cursorIsBefore(newAnchor, newHead) ? newAnchor
   1602                    : newHead);
   1603            updateMark(cm, vim, '>',
   1604                cursorIsBefore(newAnchor, newHead) ? newHead
   1605                    : newAnchor);
   1606          } else if (!operator) {
   1607            newHead = clipCursorToContent(cm, newHead);
   1608            cm.setCursor(newHead.line, newHead.ch);
   1609          }
   1610        }
   1611        if (operator) {
   1612          if (operatorArgs.lastSel) {
   1613            // Replaying a visual mode operation
   1614            newAnchor = oldAnchor;
   1615            var lastSel = operatorArgs.lastSel;
   1616            var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line);
   1617            var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch);
   1618            if (lastSel.visualLine) {
   1619              // Linewise Visual mode: The same number of lines.
   1620              newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
   1621            } else if (lastSel.visualBlock) {
   1622              // Blockwise Visual mode: The same number of lines and columns.
   1623              newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset);
   1624            } else if (lastSel.head.line == lastSel.anchor.line) {
   1625              // Normal Visual mode within one line: The same number of characters.
   1626              newHead = Pos(oldAnchor.line, oldAnchor.ch + chOffset);
   1627            } else {
   1628              // Normal Visual mode with several lines: The same number of lines, in the
   1629              // last line the same number of characters as in the last line the last time.
   1630              newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
   1631            }
   1632            vim.visualMode = true;
   1633            vim.visualLine = lastSel.visualLine;
   1634            vim.visualBlock = lastSel.visualBlock;
   1635            sel = vim.sel = {
   1636              anchor: newAnchor,
   1637              head: newHead
   1638            };
   1639            updateCmSelection(cm);
   1640          } else if (vim.visualMode) {
   1641            operatorArgs.lastSel = {
   1642              anchor: copyCursor(sel.anchor),
   1643              head: copyCursor(sel.head),
   1644              visualBlock: vim.visualBlock,
   1645              visualLine: vim.visualLine
   1646            };
   1647          }
   1648          var curStart, curEnd, linewise, mode;
   1649          var cmSel;
   1650          if (vim.visualMode) {
   1651            // Init visual op
   1652            curStart = cursorMin(sel.head, sel.anchor);
   1653            curEnd = cursorMax(sel.head, sel.anchor);
   1654            linewise = vim.visualLine || operatorArgs.linewise;
   1655            mode = vim.visualBlock ? 'block' :
   1656                   linewise ? 'line' :
   1657                   'char';
   1658            cmSel = makeCmSelection(cm, {
   1659              anchor: curStart,
   1660              head: curEnd
   1661            }, mode);
   1662            if (linewise) {
   1663              var ranges = cmSel.ranges;
   1664              if (mode == 'block') {
   1665                // Linewise operators in visual block mode extend to end of line
   1666                for (var i = 0; i < ranges.length; i++) {
   1667                  ranges[i].head.ch = lineLength(cm, ranges[i].head.line);
   1668                }
   1669              } else if (mode == 'line') {
   1670                ranges[0].head = Pos(ranges[0].head.line + 1, 0);
   1671              }
   1672            }
   1673          } else {
   1674            // Init motion op
   1675            curStart = copyCursor(newAnchor || oldAnchor);
   1676            curEnd = copyCursor(newHead || oldHead);
   1677            if (cursorIsBefore(curEnd, curStart)) {
   1678              var tmp = curStart;
   1679              curStart = curEnd;
   1680              curEnd = tmp;
   1681            }
   1682            linewise = motionArgs.linewise || operatorArgs.linewise;
   1683            if (linewise) {
   1684              // Expand selection to entire line.
   1685              expandSelectionToLine(cm, curStart, curEnd);
   1686            } else if (motionArgs.forward) {
   1687              // Clip to trailing newlines only if the motion goes forward.
   1688              clipToLine(cm, curStart, curEnd);
   1689            }
   1690            mode = 'char';
   1691            var exclusive = !motionArgs.inclusive || linewise;
   1692            cmSel = makeCmSelection(cm, {
   1693              anchor: curStart,
   1694              head: curEnd
   1695            }, mode, exclusive);
   1696          }
   1697          cm.setSelections(cmSel.ranges, cmSel.primary);
   1698          vim.lastMotion = null;
   1699          operatorArgs.repeat = repeat; // For indent in visual mode.
   1700          operatorArgs.registerName = registerName;
   1701          // Keep track of linewise as it affects how paste and change behave.
   1702          operatorArgs.linewise = linewise;
   1703          var operatorMoveTo = operators[operator](
   1704            cm, operatorArgs, cmSel.ranges, oldAnchor, newHead);
   1705          if (vim.visualMode) {
   1706            exitVisualMode(cm, operatorMoveTo != null);
   1707          }
   1708          if (operatorMoveTo) {
   1709            cm.setCursor(operatorMoveTo);
   1710          }
   1711        }
   1712      },
   1713      recordLastEdit: function(vim, inputState, actionCommand) {
   1714        var macroModeState = vimGlobalState.macroModeState;
   1715        if (macroModeState.isPlaying) { return; }
   1716        vim.lastEditInputState = inputState;
   1717        vim.lastEditActionCommand = actionCommand;
   1718        macroModeState.lastInsertModeChanges.changes = [];
   1719        macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
   1720        macroModeState.lastInsertModeChanges.visualBlock = vim.visualBlock ? vim.sel.head.line - vim.sel.anchor.line : 0;
   1721      }
   1722    };
   1723 
   1724    /**
   1725     * typedef {Object{line:number,ch:number}} Cursor An object containing the
   1726     *     position of the cursor.
   1727     */
   1728    // All of the functions below return Cursor objects.
   1729    var motions = {
   1730      moveToTopLine: function(cm, _head, motionArgs) {
   1731        var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
   1732        return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
   1733      },
   1734      moveToMiddleLine: function(cm) {
   1735        var range = getUserVisibleLines(cm);
   1736        var line = Math.floor((range.top + range.bottom) * 0.5);
   1737        return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
   1738      },
   1739      moveToBottomLine: function(cm, _head, motionArgs) {
   1740        var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
   1741        return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
   1742      },
   1743      expandToLine: function(_cm, head, motionArgs) {
   1744        // Expands forward to end of line, and then to next line if repeat is
   1745        // >1. Does not handle backward motion!
   1746        var cur = head;
   1747        return Pos(cur.line + motionArgs.repeat - 1, Infinity);
   1748      },
   1749      findNext: function(cm, _head, motionArgs) {
   1750        var state = getSearchState(cm);
   1751        var query = state.getQuery();
   1752        if (!query) {
   1753          return;
   1754        }
   1755        var prev = !motionArgs.forward;
   1756        // If search is initiated with ? instead of /, negate direction.
   1757        prev = (state.isReversed()) ? !prev : prev;
   1758        highlightSearchMatches(cm, query);
   1759        return findNext(cm, prev/** prev */, query, motionArgs.repeat);
   1760      },
   1761      goToMark: function(cm, _head, motionArgs, vim) {
   1762        var pos = getMarkPos(cm, vim, motionArgs.selectedCharacter);
   1763        if (pos) {
   1764          return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos;
   1765        }
   1766        return null;
   1767      },
   1768      moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) {
   1769        if (vim.visualBlock && motionArgs.sameLine) {
   1770          var sel = vim.sel;
   1771          return [
   1772            clipCursorToContent(cm, Pos(sel.anchor.line, sel.head.ch)),
   1773            clipCursorToContent(cm, Pos(sel.head.line, sel.anchor.ch))
   1774          ];
   1775        } else {
   1776          return ([vim.sel.head, vim.sel.anchor]);
   1777        }
   1778      },
   1779      jumpToMark: function(cm, head, motionArgs, vim) {
   1780        var best = head;
   1781        for (var i = 0; i < motionArgs.repeat; i++) {
   1782          var cursor = best;
   1783          for (var key in vim.marks) {
   1784            if (!isLowerCase(key)) {
   1785              continue;
   1786            }
   1787            var mark = vim.marks[key].find();
   1788            var isWrongDirection = (motionArgs.forward) ?
   1789              cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);
   1790 
   1791            if (isWrongDirection) {
   1792              continue;
   1793            }
   1794            if (motionArgs.linewise && (mark.line == cursor.line)) {
   1795              continue;
   1796            }
   1797 
   1798            var equal = cursorEqual(cursor, best);
   1799            var between = (motionArgs.forward) ?
   1800              cursorIsBetween(cursor, mark, best) :
   1801              cursorIsBetween(best, mark, cursor);
   1802 
   1803            if (equal || between) {
   1804              best = mark;
   1805            }
   1806          }
   1807        }
   1808 
   1809        if (motionArgs.linewise) {
   1810          // Vim places the cursor on the first non-whitespace character of
   1811          // the line if there is one, else it places the cursor at the end
   1812          // of the line, regardless of whether a mark was found.
   1813          best = Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line)));
   1814        }
   1815        return best;
   1816      },
   1817      moveByCharacters: function(_cm, head, motionArgs) {
   1818        var cur = head;
   1819        var repeat = motionArgs.repeat;
   1820        var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
   1821        return Pos(cur.line, ch);
   1822      },
   1823      moveByLines: function(cm, head, motionArgs, vim) {
   1824        var cur = head;
   1825        var endCh = cur.ch;
   1826        // Depending what our last motion was, we may want to do different
   1827        // things. If our last motion was moving vertically, we want to
   1828        // preserve the HPos from our last horizontal move.  If our last motion
   1829        // was going to the end of a line, moving vertically we should go to
   1830        // the end of the line, etc.
   1831        switch (vim.lastMotion) {
   1832          case this.moveByLines:
   1833          case this.moveByDisplayLines:
   1834          case this.moveByScroll:
   1835          case this.moveToColumn:
   1836          case this.moveToEol:
   1837            endCh = vim.lastHPos;
   1838            break;
   1839          default:
   1840            vim.lastHPos = endCh;
   1841        }
   1842        var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
   1843        var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
   1844        var first = cm.firstLine();
   1845        var last = cm.lastLine();
   1846        // Vim go to line begin or line end when cursor at first/last line and
   1847        // move to previous/next line is triggered.
   1848        if (line < first && cur.line == first){
   1849          return this.moveToStartOfLine(cm, head, motionArgs, vim);
   1850        }else if (line > last && cur.line == last){
   1851            return this.moveToEol(cm, head, motionArgs, vim, true);
   1852        }
   1853        if (motionArgs.toFirstChar){
   1854          endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
   1855          vim.lastHPos = endCh;
   1856        }
   1857        vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left;
   1858        return Pos(line, endCh);
   1859      },
   1860      moveByDisplayLines: function(cm, head, motionArgs, vim) {
   1861        var cur = head;
   1862        switch (vim.lastMotion) {
   1863          case this.moveByDisplayLines:
   1864          case this.moveByScroll:
   1865          case this.moveByLines:
   1866          case this.moveToColumn:
   1867          case this.moveToEol:
   1868            break;
   1869          default:
   1870            vim.lastHSPos = cm.charCoords(cur,'div').left;
   1871        }
   1872        var repeat = motionArgs.repeat;
   1873        var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);
   1874        if (res.hitSide) {
   1875          if (motionArgs.forward) {
   1876            var lastCharCoords = cm.charCoords(res, 'div');
   1877            var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };
   1878            var res = cm.coordsChar(goalCoords, 'div');
   1879          } else {
   1880            var resCoords = cm.charCoords(Pos(cm.firstLine(), 0), 'div');
   1881            resCoords.left = vim.lastHSPos;
   1882            res = cm.coordsChar(resCoords, 'div');
   1883          }
   1884        }
   1885        vim.lastHPos = res.ch;
   1886        return res;
   1887      },
   1888      moveByPage: function(cm, head, motionArgs) {
   1889        // CodeMirror only exposes functions that move the cursor page down, so
   1890        // doing this bad hack to move the cursor and move it back. evalInput
   1891        // will move the cursor to where it should be in the end.
   1892        var curStart = head;
   1893        var repeat = motionArgs.repeat;
   1894        return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page');
   1895      },
   1896      moveByParagraph: function(cm, head, motionArgs) {
   1897        var dir = motionArgs.forward ? 1 : -1;
   1898        return findParagraph(cm, head, motionArgs.repeat, dir);
   1899      },
   1900      moveBySentence: function(cm, head, motionArgs) {
   1901        var dir = motionArgs.forward ? 1 : -1;
   1902        return findSentence(cm, head, motionArgs.repeat, dir);
   1903      },
   1904      moveByScroll: function(cm, head, motionArgs, vim) {
   1905        var scrollbox = cm.getScrollInfo();
   1906        var curEnd = null;
   1907        var repeat = motionArgs.repeat;
   1908        if (!repeat) {
   1909          repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
   1910        }
   1911        var orig = cm.charCoords(head, 'local');
   1912        motionArgs.repeat = repeat;
   1913        var curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim);
   1914        if (!curEnd) {
   1915          return null;
   1916        }
   1917        var dest = cm.charCoords(curEnd, 'local');
   1918        cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
   1919        return curEnd;
   1920      },
   1921      moveByWords: function(cm, head, motionArgs) {
   1922        return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward,
   1923            !!motionArgs.wordEnd, !!motionArgs.bigWord);
   1924      },
   1925      moveTillCharacter: function(cm, _head, motionArgs) {
   1926        var repeat = motionArgs.repeat;
   1927        var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
   1928            motionArgs.selectedCharacter);
   1929        var increment = motionArgs.forward ? -1 : 1;
   1930        recordLastCharacterSearch(increment, motionArgs);
   1931        if (!curEnd) return null;
   1932        curEnd.ch += increment;
   1933        return curEnd;
   1934      },
   1935      moveToCharacter: function(cm, head, motionArgs) {
   1936        var repeat = motionArgs.repeat;
   1937        recordLastCharacterSearch(0, motionArgs);
   1938        return moveToCharacter(cm, repeat, motionArgs.forward,
   1939            motionArgs.selectedCharacter) || head;
   1940      },
   1941      moveToSymbol: function(cm, head, motionArgs) {
   1942        var repeat = motionArgs.repeat;
   1943        return findSymbol(cm, repeat, motionArgs.forward,
   1944            motionArgs.selectedCharacter) || head;
   1945      },
   1946      moveToColumn: function(cm, head, motionArgs, vim) {
   1947        var repeat = motionArgs.repeat;
   1948        // repeat is equivalent to which column we want to move to!
   1949        vim.lastHPos = repeat - 1;
   1950        vim.lastHSPos = cm.charCoords(head,'div').left;
   1951        return moveToColumn(cm, repeat);
   1952      },
   1953      moveToEol: function(cm, head, motionArgs, vim, keepHPos) {
   1954        var cur = head;
   1955        var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity);
   1956        var end=cm.clipPos(retval);
   1957        end.ch--;
   1958        if (!keepHPos) {
   1959          vim.lastHPos = Infinity;
   1960          vim.lastHSPos = cm.charCoords(end,'div').left;
   1961        }
   1962        return retval;
   1963      },
   1964      moveToFirstNonWhiteSpaceCharacter: function(cm, head) {
   1965        // Go to the start of the line where the text begins, or the end for
   1966        // whitespace-only lines
   1967        var cursor = head;
   1968        return Pos(cursor.line,
   1969                   findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));
   1970      },
   1971      moveToMatchedSymbol: function(cm, head) {
   1972        var cursor = head;
   1973        var line = cursor.line;
   1974        var ch = cursor.ch;
   1975        var lineText = cm.getLine(line);
   1976        var symbol;
   1977        for (; ch < lineText.length; ch++) {
   1978          symbol = lineText.charAt(ch);
   1979          if (symbol && isMatchableSymbol(symbol)) {
   1980            var style = cm.getTokenTypeAt(Pos(line, ch + 1));
   1981            if (style !== "string" && style !== "comment") {
   1982              break;
   1983            }
   1984          }
   1985        }
   1986        if (ch < lineText.length) {
   1987          // Only include angle brackets in analysis if they are being matched.
   1988          var re = (ch === '<' || ch === '>') ? /[(){}[\]<>]/ : /[(){}[\]]/;
   1989          var matched = cm.findMatchingBracket(Pos(line, ch), {bracketRegex: re});
   1990          return matched.to;
   1991        } else {
   1992          return cursor;
   1993        }
   1994      },
   1995      moveToStartOfLine: function(_cm, head) {
   1996        return Pos(head.line, 0);
   1997      },
   1998      moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) {
   1999        var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
   2000        if (motionArgs.repeatIsExplicit) {
   2001          lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
   2002        }
   2003        return Pos(lineNum,
   2004                   findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)));
   2005      },
   2006      textObjectManipulation: function(cm, head, motionArgs, vim) {
   2007        // TODO: lots of possible exceptions that can be thrown here. Try da(
   2008        //     outside of a () block.
   2009        var mirroredPairs = {'(': ')', ')': '(',
   2010                             '{': '}', '}': '{',
   2011                             '[': ']', ']': '[',
   2012                             '<': '>', '>': '<'};
   2013        var selfPaired = {'\'': true, '"': true, '`': true};
   2014 
   2015        var character = motionArgs.selectedCharacter;
   2016        // 'b' refers to  '()' block.
   2017        // 'B' refers to  '{}' block.
   2018        if (character == 'b') {
   2019          character = '(';
   2020        } else if (character == 'B') {
   2021          character = '{';
   2022        }
   2023 
   2024        // Inclusive is the difference between a and i
   2025        // TODO: Instead of using the additional text object map to perform text
   2026        //     object operations, merge the map into the defaultKeyMap and use
   2027        //     motionArgs to define behavior. Define separate entries for 'aw',
   2028        //     'iw', 'a[', 'i[', etc.
   2029        var inclusive = !motionArgs.textObjectInner;
   2030 
   2031        var tmp;
   2032        if (mirroredPairs[character]) {
   2033          tmp = selectCompanionObject(cm, head, character, inclusive);
   2034        } else if (selfPaired[character]) {
   2035          tmp = findBeginningAndEnd(cm, head, character, inclusive);
   2036        } else if (character === 'W') {
   2037          tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
   2038                                                     true /** bigWord */);
   2039        } else if (character === 'w') {
   2040          tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
   2041                                                     false /** bigWord */);
   2042        } else if (character === 'p') {
   2043          tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive);
   2044          motionArgs.linewise = true;
   2045          if (vim.visualMode) {
   2046            if (!vim.visualLine) { vim.visualLine = true; }
   2047          } else {
   2048            var operatorArgs = vim.inputState.operatorArgs;
   2049            if (operatorArgs) { operatorArgs.linewise = true; }
   2050            tmp.end.line--;
   2051          }
   2052        } else {
   2053          // No text object defined for this, don't move.
   2054          return null;
   2055        }
   2056 
   2057        if (!cm.state.vim.visualMode) {
   2058          return [tmp.start, tmp.end];
   2059        } else {
   2060          return expandSelection(cm, tmp.start, tmp.end);
   2061        }
   2062      },
   2063 
   2064      repeatLastCharacterSearch: function(cm, head, motionArgs) {
   2065        var lastSearch = vimGlobalState.lastCharacterSearch;
   2066        var repeat = motionArgs.repeat;
   2067        var forward = motionArgs.forward === lastSearch.forward;
   2068        var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
   2069        cm.moveH(-increment, 'char');
   2070        motionArgs.inclusive = forward ? true : false;
   2071        var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
   2072        if (!curEnd) {
   2073          cm.moveH(increment, 'char');
   2074          return head;
   2075        }
   2076        curEnd.ch += increment;
   2077        return curEnd;
   2078      }
   2079    };
   2080 
   2081    function defineMotion(name, fn) {
   2082      motions[name] = fn;
   2083    }
   2084 
   2085    function fillArray(val, times) {
   2086      var arr = [];
   2087      for (var i = 0; i < times; i++) {
   2088        arr.push(val);
   2089      }
   2090      return arr;
   2091    }
   2092    /**
   2093     * An operator acts on a text selection. It receives the list of selections
   2094     * as input. The corresponding CodeMirror selection is guaranteed to
   2095    * match the input selection.
   2096     */
   2097    var operators = {
   2098      change: function(cm, args, ranges) {
   2099        var finalHead, text;
   2100        var vim = cm.state.vim;
   2101        if (!vim.visualMode) {
   2102          var anchor = ranges[0].anchor,
   2103              head = ranges[0].head;
   2104          text = cm.getRange(anchor, head);
   2105          var lastState = vim.lastEditInputState || {};
   2106          if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) {
   2107            // Exclude trailing whitespace if the range is not all whitespace.
   2108            var match = (/\s+$/).exec(text);
   2109            if (match && lastState.motionArgs && lastState.motionArgs.forward) {
   2110              head = offsetCursor(head, 0, - match[0].length);
   2111              text = text.slice(0, - match[0].length);
   2112            }
   2113          }
   2114          var prevLineEnd = new Pos(anchor.line - 1, Number.MAX_VALUE);
   2115          var wasLastLine = cm.firstLine() == cm.lastLine();
   2116          if (head.line > cm.lastLine() && args.linewise && !wasLastLine) {
   2117            cm.replaceRange('', prevLineEnd, head);
   2118          } else {
   2119            cm.replaceRange('', anchor, head);
   2120          }
   2121          if (args.linewise) {
   2122            // Push the next line back down, if there is a next line.
   2123            if (!wasLastLine) {
   2124              cm.setCursor(prevLineEnd);
   2125              CodeMirror.commands.newlineAndIndent(cm);
   2126            }
   2127            // make sure cursor ends up at the end of the line.
   2128            anchor.ch = Number.MAX_VALUE;
   2129          }
   2130          finalHead = anchor;
   2131        } else {
   2132          text = cm.getSelection();
   2133          var replacement = fillArray('', ranges.length);
   2134          cm.replaceSelections(replacement);
   2135          finalHead = cursorMin(ranges[0].head, ranges[0].anchor);
   2136        }
   2137        vimGlobalState.registerController.pushText(
   2138            args.registerName, 'change', text,
   2139            args.linewise, ranges.length > 1);
   2140        actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim);
   2141      },
   2142      // delete is a javascript keyword.
   2143      'delete': function(cm, args, ranges) {
   2144        var finalHead, text;
   2145        var vim = cm.state.vim;
   2146        if (!vim.visualBlock) {
   2147          var anchor = ranges[0].anchor,
   2148              head = ranges[0].head;
   2149          if (args.linewise &&
   2150              head.line != cm.firstLine() &&
   2151              anchor.line == cm.lastLine() &&
   2152              anchor.line == head.line - 1) {
   2153            // Special case for dd on last line (and first line).
   2154            if (anchor.line == cm.firstLine()) {
   2155              anchor.ch = 0;
   2156            } else {
   2157              anchor = Pos(anchor.line - 1, lineLength(cm, anchor.line - 1));
   2158            }
   2159          }
   2160          text = cm.getRange(anchor, head);
   2161          cm.replaceRange('', anchor, head);
   2162          finalHead = anchor;
   2163          if (args.linewise) {
   2164            finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor);
   2165          }
   2166        } else {
   2167          text = cm.getSelection();
   2168          var replacement = fillArray('', ranges.length);
   2169          cm.replaceSelections(replacement);
   2170          finalHead = ranges[0].anchor;
   2171        }
   2172        vimGlobalState.registerController.pushText(
   2173            args.registerName, 'delete', text,
   2174            args.linewise, vim.visualBlock);
   2175        var includeLineBreak = vim.insertMode
   2176        return clipCursorToContent(cm, finalHead, includeLineBreak);
   2177      },
   2178      indent: function(cm, args, ranges) {
   2179        var vim = cm.state.vim;
   2180        var startLine = ranges[0].anchor.line;
   2181        var endLine = vim.visualBlock ?
   2182          ranges[ranges.length - 1].anchor.line :
   2183          ranges[0].head.line;
   2184        // In visual mode, n> shifts the selection right n times, instead of
   2185        // shifting n lines right once.
   2186        var repeat = (vim.visualMode) ? args.repeat : 1;
   2187        if (args.linewise) {
   2188          // The only way to delete a newline is to delete until the start of
   2189          // the next line, so in linewise mode evalInput will include the next
   2190          // line. We don't want this in indent, so we go back a line.
   2191          endLine--;
   2192        }
   2193        for (var i = startLine; i <= endLine; i++) {
   2194          for (var j = 0; j < repeat; j++) {
   2195            cm.indentLine(i, args.indentRight);
   2196          }
   2197        }
   2198        return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
   2199      },
   2200      indentAuto: function(cm, _args, ranges) {
   2201        cm.execCommand("indentAuto");
   2202        return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
   2203      },
   2204      changeCase: function(cm, args, ranges, oldAnchor, newHead) {
   2205        var selections = cm.getSelections();
   2206        var swapped = [];
   2207        var toLower = args.toLower;
   2208        for (var j = 0; j < selections.length; j++) {
   2209          var toSwap = selections[j];
   2210          var text = '';
   2211          if (toLower === true) {
   2212            text = toSwap.toLowerCase();
   2213          } else if (toLower === false) {
   2214            text = toSwap.toUpperCase();
   2215          } else {
   2216            for (var i = 0; i < toSwap.length; i++) {
   2217              var character = toSwap.charAt(i);
   2218              text += isUpperCase(character) ? character.toLowerCase() :
   2219                  character.toUpperCase();
   2220            }
   2221          }
   2222          swapped.push(text);
   2223        }
   2224        cm.replaceSelections(swapped);
   2225        if (args.shouldMoveCursor){
   2226          return newHead;
   2227        } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) {
   2228          return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor);
   2229        } else if (args.linewise){
   2230          return oldAnchor;
   2231        } else {
   2232          return cursorMin(ranges[0].anchor, ranges[0].head);
   2233        }
   2234      },
   2235      yank: function(cm, args, ranges, oldAnchor) {
   2236        var vim = cm.state.vim;
   2237        var text = cm.getSelection();
   2238        var endPos = vim.visualMode
   2239          ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor)
   2240          : oldAnchor;
   2241        vimGlobalState.registerController.pushText(
   2242            args.registerName, 'yank',
   2243            text, args.linewise, vim.visualBlock);
   2244        return endPos;
   2245      }
   2246    };
   2247 
   2248    function defineOperator(name, fn) {
   2249      operators[name] = fn;
   2250    }
   2251 
   2252    var actions = {
   2253      jumpListWalk: function(cm, actionArgs, vim) {
   2254        if (vim.visualMode) {
   2255          return;
   2256        }
   2257        var repeat = actionArgs.repeat;
   2258        var forward = actionArgs.forward;
   2259        var jumpList = vimGlobalState.jumpList;
   2260 
   2261        var mark = jumpList.move(cm, forward ? repeat : -repeat);
   2262        var markPos = mark ? mark.find() : undefined;
   2263        markPos = markPos ? markPos : cm.getCursor();
   2264        cm.setCursor(markPos);
   2265      },
   2266      scroll: function(cm, actionArgs, vim) {
   2267        if (vim.visualMode) {
   2268          return;
   2269        }
   2270        var repeat = actionArgs.repeat || 1;
   2271        var lineHeight = cm.defaultTextHeight();
   2272        var top = cm.getScrollInfo().top;
   2273        var delta = lineHeight * repeat;
   2274        var newPos = actionArgs.forward ? top + delta : top - delta;
   2275        var cursor = copyCursor(cm.getCursor());
   2276        var cursorCoords = cm.charCoords(cursor, 'local');
   2277        if (actionArgs.forward) {
   2278          if (newPos > cursorCoords.top) {
   2279             cursor.line += (newPos - cursorCoords.top) / lineHeight;
   2280             cursor.line = Math.ceil(cursor.line);
   2281             cm.setCursor(cursor);
   2282             cursorCoords = cm.charCoords(cursor, 'local');
   2283             cm.scrollTo(null, cursorCoords.top);
   2284          } else {
   2285             // Cursor stays within bounds.  Just reposition the scroll window.
   2286             cm.scrollTo(null, newPos);
   2287          }
   2288        } else {
   2289          var newBottom = newPos + cm.getScrollInfo().clientHeight;
   2290          if (newBottom < cursorCoords.bottom) {
   2291             cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight;
   2292             cursor.line = Math.floor(cursor.line);
   2293             cm.setCursor(cursor);
   2294             cursorCoords = cm.charCoords(cursor, 'local');
   2295             cm.scrollTo(
   2296                 null, cursorCoords.bottom - cm.getScrollInfo().clientHeight);
   2297          } else {
   2298             // Cursor stays within bounds.  Just reposition the scroll window.
   2299             cm.scrollTo(null, newPos);
   2300          }
   2301        }
   2302      },
   2303      scrollToCursor: function(cm, actionArgs) {
   2304        var lineNum = cm.getCursor().line;
   2305        var charCoords = cm.charCoords(Pos(lineNum, 0), 'local');
   2306        var height = cm.getScrollInfo().clientHeight;
   2307        var y = charCoords.top;
   2308        var lineHeight = charCoords.bottom - y;
   2309        switch (actionArgs.position) {
   2310          case 'center': y = y - (height / 2) + lineHeight;
   2311            break;
   2312          case 'bottom': y = y - height + lineHeight;
   2313            break;
   2314        }
   2315        cm.scrollTo(null, y);
   2316      },
   2317      replayMacro: function(cm, actionArgs, vim) {
   2318        var registerName = actionArgs.selectedCharacter;
   2319        var repeat = actionArgs.repeat;
   2320        var macroModeState = vimGlobalState.macroModeState;
   2321        if (registerName == '@') {
   2322          registerName = macroModeState.latestRegister;
   2323        } else {
   2324          macroModeState.latestRegister = registerName;
   2325        }
   2326        while(repeat--){
   2327          executeMacroRegister(cm, vim, macroModeState, registerName);
   2328        }
   2329      },
   2330      enterMacroRecordMode: function(cm, actionArgs) {
   2331        var macroModeState = vimGlobalState.macroModeState;
   2332        var registerName = actionArgs.selectedCharacter;
   2333        if (vimGlobalState.registerController.isValidRegister(registerName)) {
   2334          macroModeState.enterMacroRecordMode(cm, registerName);
   2335        }
   2336      },
   2337      toggleOverwrite: function(cm) {
   2338        if (!cm.state.overwrite) {
   2339          cm.toggleOverwrite(true);
   2340          cm.setOption('keyMap', 'vim-replace');
   2341          CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
   2342        } else {
   2343          cm.toggleOverwrite(false);
   2344          cm.setOption('keyMap', 'vim-insert');
   2345          CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
   2346        }
   2347      },
   2348      enterInsertMode: function(cm, actionArgs, vim) {
   2349        if (cm.getOption('readOnly')) { return; }
   2350        vim.insertMode = true;
   2351        vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
   2352        var insertAt = (actionArgs) ? actionArgs.insertAt : null;
   2353        var sel = vim.sel;
   2354        var head = actionArgs.head || cm.getCursor('head');
   2355        var height = cm.listSelections().length;
   2356        if (insertAt == 'eol') {
   2357          head = Pos(head.line, lineLength(cm, head.line));
   2358        } else if (insertAt == 'charAfter') {
   2359          head = offsetCursor(head, 0, 1);
   2360        } else if (insertAt == 'firstNonBlank') {
   2361          head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head);
   2362        } else if (insertAt == 'startOfSelectedArea') {
   2363          if (!vim.visualMode)
   2364              return;
   2365          if (!vim.visualBlock) {
   2366            if (sel.head.line < sel.anchor.line) {
   2367              head = sel.head;
   2368            } else {
   2369              head = Pos(sel.anchor.line, 0);
   2370            }
   2371          } else {
   2372            head = Pos(
   2373                Math.min(sel.head.line, sel.anchor.line),
   2374                Math.min(sel.head.ch, sel.anchor.ch));
   2375            height = Math.abs(sel.head.line - sel.anchor.line) + 1;
   2376          }
   2377        } else if (insertAt == 'endOfSelectedArea') {
   2378            if (!vim.visualMode)
   2379              return;
   2380          if (!vim.visualBlock) {
   2381            if (sel.head.line >= sel.anchor.line) {
   2382              head = offsetCursor(sel.head, 0, 1);
   2383            } else {
   2384              head = Pos(sel.anchor.line, 0);
   2385            }
   2386          } else {
   2387            head = Pos(
   2388                Math.min(sel.head.line, sel.anchor.line),
   2389                Math.max(sel.head.ch + 1, sel.anchor.ch));
   2390            height = Math.abs(sel.head.line - sel.anchor.line) + 1;
   2391          }
   2392        } else if (insertAt == 'inplace') {
   2393          if (vim.visualMode){
   2394            return;
   2395          }
   2396        }
   2397        cm.setOption('disableInput', false);
   2398        if (actionArgs && actionArgs.replace) {
   2399          // Handle Replace-mode as a special case of insert mode.
   2400          cm.toggleOverwrite(true);
   2401          cm.setOption('keyMap', 'vim-replace');
   2402          CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
   2403        } else {
   2404          cm.toggleOverwrite(false);
   2405          cm.setOption('keyMap', 'vim-insert');
   2406          CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
   2407        }
   2408        if (!vimGlobalState.macroModeState.isPlaying) {
   2409          // Only record if not replaying.
   2410          cm.on('change', onChange);
   2411          CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
   2412        }
   2413        if (vim.visualMode) {
   2414          exitVisualMode(cm);
   2415        }
   2416        selectForInsert(cm, head, height);
   2417      },
   2418      toggleVisualMode: function(cm, actionArgs, vim) {
   2419        var repeat = actionArgs.repeat;
   2420        var anchor = cm.getCursor();
   2421        var head;
   2422        // TODO: The repeat should actually select number of characters/lines
   2423        //     equal to the repeat times the size of the previous visual
   2424        //     operation.
   2425        if (!vim.visualMode) {
   2426          // Entering visual mode
   2427          vim.visualMode = true;
   2428          vim.visualLine = !!actionArgs.linewise;
   2429          vim.visualBlock = !!actionArgs.blockwise;
   2430          head = clipCursorToContent(
   2431              cm, Pos(anchor.line, anchor.ch + repeat - 1),
   2432              true /** includeLineBreak */);
   2433          vim.sel = {
   2434            anchor: anchor,
   2435            head: head
   2436          };
   2437          CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
   2438          updateCmSelection(cm);
   2439          updateMark(cm, vim, '<', cursorMin(anchor, head));
   2440          updateMark(cm, vim, '>', cursorMax(anchor, head));
   2441        } else if (vim.visualLine ^ actionArgs.linewise ||
   2442            vim.visualBlock ^ actionArgs.blockwise) {
   2443          // Toggling between modes
   2444          vim.visualLine = !!actionArgs.linewise;
   2445          vim.visualBlock = !!actionArgs.blockwise;
   2446          CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
   2447          updateCmSelection(cm);
   2448        } else {
   2449          exitVisualMode(cm);
   2450        }
   2451      },
   2452      reselectLastSelection: function(cm, _actionArgs, vim) {
   2453        var lastSelection = vim.lastSelection;
   2454        if (vim.visualMode) {
   2455          updateLastSelection(cm, vim);
   2456        }
   2457        if (lastSelection) {
   2458          var anchor = lastSelection.anchorMark.find();
   2459          var head = lastSelection.headMark.find();
   2460          if (!anchor || !head) {
   2461            // If the marks have been destroyed due to edits, do nothing.
   2462            return;
   2463          }
   2464          vim.sel = {
   2465            anchor: anchor,
   2466            head: head
   2467          };
   2468          vim.visualMode = true;
   2469          vim.visualLine = lastSelection.visualLine;
   2470          vim.visualBlock = lastSelection.visualBlock;
   2471          updateCmSelection(cm);
   2472          updateMark(cm, vim, '<', cursorMin(anchor, head));
   2473          updateMark(cm, vim, '>', cursorMax(anchor, head));
   2474          CodeMirror.signal(cm, 'vim-mode-change', {
   2475            mode: 'visual',
   2476            subMode: vim.visualLine ? 'linewise' :
   2477                     vim.visualBlock ? 'blockwise' : ''});
   2478        }
   2479      },
   2480      joinLines: function(cm, actionArgs, vim) {
   2481        var curStart, curEnd;
   2482        if (vim.visualMode) {
   2483          curStart = cm.getCursor('anchor');
   2484          curEnd = cm.getCursor('head');
   2485          if (cursorIsBefore(curEnd, curStart)) {
   2486            var tmp = curEnd;
   2487            curEnd = curStart;
   2488            curStart = tmp;
   2489          }
   2490          curEnd.ch = lineLength(cm, curEnd.line) - 1;
   2491        } else {
   2492          // Repeat is the number of lines to join. Minimum 2 lines.
   2493          var repeat = Math.max(actionArgs.repeat, 2);
   2494          curStart = cm.getCursor();
   2495          curEnd = clipCursorToContent(cm, Pos(curStart.line + repeat - 1,
   2496                                               Infinity));
   2497        }
   2498        var finalCh = 0;
   2499        for (var i = curStart.line; i < curEnd.line; i++) {
   2500          finalCh = lineLength(cm, curStart.line);
   2501          var tmp = Pos(curStart.line + 1,
   2502                        lineLength(cm, curStart.line + 1));
   2503          var text = cm.getRange(curStart, tmp);
   2504          text = text.replace(/\n\s*/g, ' ');
   2505          cm.replaceRange(text, curStart, tmp);
   2506        }
   2507        var curFinalPos = Pos(curStart.line, finalCh);
   2508        if (vim.visualMode) {
   2509          exitVisualMode(cm, false);
   2510        }
   2511        cm.setCursor(curFinalPos);
   2512      },
   2513      newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
   2514        vim.insertMode = true;
   2515        var insertAt = copyCursor(cm.getCursor());
   2516        if (insertAt.line === cm.firstLine() && !actionArgs.after) {
   2517          // Special case for inserting newline before start of document.
   2518          cm.replaceRange('\n', Pos(cm.firstLine(), 0));
   2519          cm.setCursor(cm.firstLine(), 0);
   2520        } else {
   2521          insertAt.line = (actionArgs.after) ? insertAt.line :
   2522              insertAt.line - 1;
   2523          insertAt.ch = lineLength(cm, insertAt.line);
   2524          cm.setCursor(insertAt);
   2525          var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||
   2526              CodeMirror.commands.newlineAndIndent;
   2527          newlineFn(cm);
   2528        }
   2529        this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);
   2530      },
   2531      paste: function(cm, actionArgs, vim) {
   2532        var cur = copyCursor(cm.getCursor());
   2533        var register = vimGlobalState.registerController.getRegister(
   2534            actionArgs.registerName);
   2535        var text = register.toString();
   2536        if (!text) {
   2537          return;
   2538        }
   2539        if (actionArgs.matchIndent) {
   2540          var tabSize = cm.getOption("tabSize");
   2541          // length that considers tabs and tabSize
   2542          var whitespaceLength = function(str) {
   2543            var tabs = (str.split("\t").length - 1);
   2544            var spaces = (str.split(" ").length - 1);
   2545            return tabs * tabSize + spaces * 1;
   2546          };
   2547          var currentLine = cm.getLine(cm.getCursor().line);
   2548          var indent = whitespaceLength(currentLine.match(/^\s*/)[0]);
   2549          // chomp last newline b/c don't want it to match /^\s*/gm
   2550          var chompedText = text.replace(/\n$/, '');
   2551          var wasChomped = text !== chompedText;
   2552          var firstIndent = whitespaceLength(text.match(/^\s*/)[0]);
   2553          var text = chompedText.replace(/^\s*/gm, function(wspace) {
   2554            var newIndent = indent + (whitespaceLength(wspace) - firstIndent);
   2555            if (newIndent < 0) {
   2556              return "";
   2557            }
   2558            else if (cm.getOption("indentWithTabs")) {
   2559              var quotient = Math.floor(newIndent / tabSize);
   2560              return Array(quotient + 1).join('\t');
   2561            }
   2562            else {
   2563              return Array(newIndent + 1).join(' ');
   2564            }
   2565          });
   2566          text += wasChomped ? "\n" : "";
   2567        }
   2568        if (actionArgs.repeat > 1) {
   2569          var text = Array(actionArgs.repeat + 1).join(text);
   2570        }
   2571        var linewise = register.linewise;
   2572        var blockwise = register.blockwise;
   2573        if (blockwise) {
   2574          text = text.split('\n');
   2575          if (linewise) {
   2576              text.pop();
   2577          }
   2578          for (var i = 0; i < text.length; i++) {
   2579            text[i] = (text[i] == '') ? ' ' : text[i];
   2580          }
   2581          cur.ch += actionArgs.after ? 1 : 0;
   2582          cur.ch = Math.min(lineLength(cm, cur.line), cur.ch);
   2583        } else if (linewise) {
   2584          if(vim.visualMode) {
   2585            text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n';
   2586          } else if (actionArgs.after) {
   2587            // Move the newline at the end to the start instead, and paste just
   2588            // before the newline character of the line we are on right now.
   2589            text = '\n' + text.slice(0, text.length - 1);
   2590            cur.ch = lineLength(cm, cur.line);
   2591          } else {
   2592            cur.ch = 0;
   2593          }
   2594        } else {
   2595          cur.ch += actionArgs.after ? 1 : 0;
   2596        }
   2597        var curPosFinal;
   2598        var idx;
   2599        if (vim.visualMode) {
   2600          //  save the pasted text for reselection if the need arises
   2601          vim.lastPastedText = text;
   2602          var lastSelectionCurEnd;
   2603          var selectedArea = getSelectedAreaRange(cm, vim);
   2604          var selectionStart = selectedArea[0];
   2605          var selectionEnd = selectedArea[1];
   2606          var selectedText = cm.getSelection();
   2607          var selections = cm.listSelections();
   2608          var emptyStrings = new Array(selections.length).join('1').split('1');
   2609          // save the curEnd marker before it get cleared due to cm.replaceRange.
   2610          if (vim.lastSelection) {
   2611            lastSelectionCurEnd = vim.lastSelection.headMark.find();
   2612          }
   2613          // push the previously selected text to unnamed register
   2614          vimGlobalState.registerController.unnamedRegister.setText(selectedText);
   2615          if (blockwise) {
   2616            // first delete the selected text
   2617            cm.replaceSelections(emptyStrings);
   2618            // Set new selections as per the block length of the yanked text
   2619            selectionEnd = Pos(selectionStart.line + text.length-1, selectionStart.ch);
   2620            cm.setCursor(selectionStart);
   2621            selectBlock(cm, selectionEnd);
   2622            cm.replaceSelections(text);
   2623            curPosFinal = selectionStart;
   2624          } else if (vim.visualBlock) {
   2625            cm.replaceSelections(emptyStrings);
   2626            cm.setCursor(selectionStart);
   2627            cm.replaceRange(text, selectionStart, selectionStart);
   2628            curPosFinal = selectionStart;
   2629          } else {
   2630            cm.replaceRange(text, selectionStart, selectionEnd);
   2631            curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1);
   2632          }
   2633          // restore the the curEnd marker
   2634          if(lastSelectionCurEnd) {
   2635            vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd);
   2636          }
   2637          if (linewise) {
   2638            curPosFinal.ch=0;
   2639          }
   2640        } else {
   2641          if (blockwise) {
   2642            cm.setCursor(cur);
   2643            for (var i = 0; i < text.length; i++) {
   2644              var line = cur.line+i;
   2645              if (line > cm.lastLine()) {
   2646                cm.replaceRange('\n',  Pos(line, 0));
   2647              }
   2648              var lastCh = lineLength(cm, line);
   2649              if (lastCh < cur.ch) {
   2650                extendLineToColumn(cm, line, cur.ch);
   2651              }
   2652            }
   2653            cm.setCursor(cur);
   2654            selectBlock(cm, Pos(cur.line + text.length-1, cur.ch));
   2655            cm.replaceSelections(text);
   2656            curPosFinal = cur;
   2657          } else {
   2658            cm.replaceRange(text, cur);
   2659            // Now fine tune the cursor to where we want it.
   2660            if (linewise && actionArgs.after) {
   2661              curPosFinal = Pos(
   2662              cur.line + 1,
   2663              findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)));
   2664            } else if (linewise && !actionArgs.after) {
   2665              curPosFinal = Pos(
   2666                cur.line,
   2667                findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
   2668            } else if (!linewise && actionArgs.after) {
   2669              idx = cm.indexFromPos(cur);
   2670              curPosFinal = cm.posFromIndex(idx + text.length - 1);
   2671            } else {
   2672              idx = cm.indexFromPos(cur);
   2673              curPosFinal = cm.posFromIndex(idx + text.length);
   2674            }
   2675          }
   2676        }
   2677        if (vim.visualMode) {
   2678          exitVisualMode(cm, false);
   2679        }
   2680        cm.setCursor(curPosFinal);
   2681      },
   2682      undo: function(cm, actionArgs) {
   2683        cm.operation(function() {
   2684          repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
   2685          cm.setCursor(cm.getCursor('anchor'));
   2686        });
   2687      },
   2688      redo: function(cm, actionArgs) {
   2689        repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
   2690      },
   2691      setRegister: function(_cm, actionArgs, vim) {
   2692        vim.inputState.registerName = actionArgs.selectedCharacter;
   2693      },
   2694      setMark: function(cm, actionArgs, vim) {
   2695        var markName = actionArgs.selectedCharacter;
   2696        updateMark(cm, vim, markName, cm.getCursor());
   2697      },
   2698      replace: function(cm, actionArgs, vim) {
   2699        var replaceWith = actionArgs.selectedCharacter;
   2700        var curStart = cm.getCursor();
   2701        var replaceTo;
   2702        var curEnd;
   2703        var selections = cm.listSelections();
   2704        if (vim.visualMode) {
   2705          curStart = cm.getCursor('start');
   2706          curEnd = cm.getCursor('end');
   2707        } else {
   2708          var line = cm.getLine(curStart.line);
   2709          replaceTo = curStart.ch + actionArgs.repeat;
   2710          if (replaceTo > line.length) {
   2711            replaceTo=line.length;
   2712          }
   2713          curEnd = Pos(curStart.line, replaceTo);
   2714        }
   2715        if (replaceWith=='\n') {
   2716          if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
   2717          // special case, where vim help says to replace by just one line-break
   2718          (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
   2719        } else {
   2720          var replaceWithStr = cm.getRange(curStart, curEnd);
   2721          //replace all characters in range by selected, but keep linebreaks
   2722          replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith);
   2723          if (vim.visualBlock) {
   2724            // Tabs are split in visua block before replacing
   2725            var spaces = new Array(cm.getOption("tabSize")+1).join(' ');
   2726            replaceWithStr = cm.getSelection();
   2727            replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n');
   2728            cm.replaceSelections(replaceWithStr);
   2729          } else {
   2730            cm.replaceRange(replaceWithStr, curStart, curEnd);
   2731          }
   2732          if (vim.visualMode) {
   2733            curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ?
   2734                         selections[0].anchor : selections[0].head;
   2735            cm.setCursor(curStart);
   2736            exitVisualMode(cm, false);
   2737          } else {
   2738            cm.setCursor(offsetCursor(curEnd, 0, -1));
   2739          }
   2740        }
   2741      },
   2742      incrementNumberToken: function(cm, actionArgs) {
   2743        var cur = cm.getCursor();
   2744        var lineStr = cm.getLine(cur.line);
   2745        var re = /(-?)(?:(0x)([\da-f]+)|(0b|0|)(\d+))/gi;
   2746        var match;
   2747        var start;
   2748        var end;
   2749        var numberStr;
   2750        while ((match = re.exec(lineStr)) !== null) {
   2751          start = match.index;
   2752          end = start + match[0].length;
   2753          if (cur.ch < end)break;
   2754        }
   2755        if (!actionArgs.backtrack && (end <= cur.ch))return;
   2756        if (match) {
   2757          var baseStr = match[2] || match[4]
   2758          var digits = match[3] || match[5]
   2759          var increment = actionArgs.increase ? 1 : -1;
   2760          var base = {'0b': 2, '0': 8, '': 10, '0x': 16}[baseStr.toLowerCase()];
   2761          var number = parseInt(match[1] + digits, base) + (increment * actionArgs.repeat);
   2762          numberStr = number.toString(base);
   2763          var zeroPadding = baseStr ? new Array(digits.length - numberStr.length + 1 + match[1].length).join('0') : ''
   2764          if (numberStr.charAt(0) === '-') {
   2765            numberStr = '-' + baseStr + zeroPadding + numberStr.substr(1);
   2766          } else {
   2767            numberStr = baseStr + zeroPadding + numberStr;
   2768          }
   2769          var from = Pos(cur.line, start);
   2770          var to = Pos(cur.line, end);
   2771          cm.replaceRange(numberStr, from, to);
   2772        } else {
   2773          return;
   2774        }
   2775        cm.setCursor(Pos(cur.line, start + numberStr.length - 1));
   2776      },
   2777      repeatLastEdit: function(cm, actionArgs, vim) {
   2778        var lastEditInputState = vim.lastEditInputState;
   2779        if (!lastEditInputState) { return; }
   2780        var repeat = actionArgs.repeat;
   2781        if (repeat && actionArgs.repeatIsExplicit) {
   2782          vim.lastEditInputState.repeatOverride = repeat;
   2783        } else {
   2784          repeat = vim.lastEditInputState.repeatOverride || repeat;
   2785        }
   2786        repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
   2787      },
   2788      indent: function(cm, actionArgs) {
   2789        cm.indentLine(cm.getCursor().line, actionArgs.indentRight);
   2790      },
   2791      exitInsertMode: exitInsertMode
   2792    };
   2793 
   2794    function defineAction(name, fn) {
   2795      actions[name] = fn;
   2796    }
   2797 
   2798    /*
   2799     * Below are miscellaneous utility functions used by vim.js
   2800     */
   2801 
   2802    /**
   2803     * Clips cursor to ensure that line is within the buffer's range
   2804     * If includeLineBreak is true, then allow cur.ch == lineLength.
   2805     */
   2806    function clipCursorToContent(cm, cur, includeLineBreak) {
   2807      var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );
   2808      var maxCh = lineLength(cm, line) - 1;
   2809      maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
   2810      var ch = Math.min(Math.max(0, cur.ch), maxCh);
   2811      return Pos(line, ch);
   2812    }
   2813    function copyArgs(args) {
   2814      var ret = {};
   2815      for (var prop in args) {
   2816        if (args.hasOwnProperty(prop)) {
   2817          ret[prop] = args[prop];
   2818        }
   2819      }
   2820      return ret;
   2821    }
   2822    function offsetCursor(cur, offsetLine, offsetCh) {
   2823      if (typeof offsetLine === 'object') {
   2824        offsetCh = offsetLine.ch;
   2825        offsetLine = offsetLine.line;
   2826      }
   2827      return Pos(cur.line + offsetLine, cur.ch + offsetCh);
   2828    }
   2829    function commandMatches(keys, keyMap, context, inputState) {
   2830      // Partial matches are not applied. They inform the key handler
   2831      // that the current key sequence is a subsequence of a valid key
   2832      // sequence, so that the key buffer is not cleared.
   2833      var match, partial = [], full = [];
   2834      for (var i = 0; i < keyMap.length; i++) {
   2835        var command = keyMap[i];
   2836        if (context == 'insert' && command.context != 'insert' ||
   2837            command.context && command.context != context ||
   2838            inputState.operator && command.type == 'action' ||
   2839            !(match = commandMatch(keys, command.keys))) { continue; }
   2840        if (match == 'partial') { partial.push(command); }
   2841        if (match == 'full') { full.push(command); }
   2842      }
   2843      return {
   2844        partial: partial.length && partial,
   2845        full: full.length && full
   2846      };
   2847    }
   2848    function commandMatch(pressed, mapped) {
   2849      if (mapped.slice(-11) == '<character>') {
   2850        // Last character matches anything.
   2851        var prefixLen = mapped.length - 11;
   2852        var pressedPrefix = pressed.slice(0, prefixLen);
   2853        var mappedPrefix = mapped.slice(0, prefixLen);
   2854        return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' :
   2855               mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false;
   2856      } else {
   2857        return pressed == mapped ? 'full' :
   2858               mapped.indexOf(pressed) == 0 ? 'partial' : false;
   2859      }
   2860    }
   2861    function lastChar(keys) {
   2862      var match = /^.*(<[^>]+>)$/.exec(keys);
   2863      var selectedCharacter = match ? match[1] : keys.slice(-1);
   2864      if (selectedCharacter.length > 1){
   2865        switch(selectedCharacter){
   2866          case '<CR>':
   2867            selectedCharacter='\n';
   2868            break;
   2869          case '<Space>':
   2870            selectedCharacter=' ';
   2871            break;
   2872          default:
   2873            selectedCharacter='';
   2874            break;
   2875        }
   2876      }
   2877      return selectedCharacter;
   2878    }
   2879    function repeatFn(cm, fn, repeat) {
   2880      return function() {
   2881        for (var i = 0; i < repeat; i++) {
   2882          fn(cm);
   2883        }
   2884      };
   2885    }
   2886    function copyCursor(cur) {
   2887      return Pos(cur.line, cur.ch);
   2888    }
   2889    function cursorEqual(cur1, cur2) {
   2890      return cur1.ch == cur2.ch && cur1.line == cur2.line;
   2891    }
   2892    function cursorIsBefore(cur1, cur2) {
   2893      if (cur1.line < cur2.line) {
   2894        return true;
   2895      }
   2896      if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
   2897        return true;
   2898      }
   2899      return false;
   2900    }
   2901    function cursorMin(cur1, cur2) {
   2902      if (arguments.length > 2) {
   2903        cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1));
   2904      }
   2905      return cursorIsBefore(cur1, cur2) ? cur1 : cur2;
   2906    }
   2907    function cursorMax(cur1, cur2) {
   2908      if (arguments.length > 2) {
   2909        cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1));
   2910      }
   2911      return cursorIsBefore(cur1, cur2) ? cur2 : cur1;
   2912    }
   2913    function cursorIsBetween(cur1, cur2, cur3) {
   2914      // returns true if cur2 is between cur1 and cur3.
   2915      var cur1before2 = cursorIsBefore(cur1, cur2);
   2916      var cur2before3 = cursorIsBefore(cur2, cur3);
   2917      return cur1before2 && cur2before3;
   2918    }
   2919    function lineLength(cm, lineNum) {
   2920      return cm.getLine(lineNum).length;
   2921    }
   2922    function trim(s) {
   2923      if (s.trim) {
   2924        return s.trim();
   2925      }
   2926      return s.replace(/^\s+|\s+$/g, '');
   2927    }
   2928    function escapeRegex(s) {
   2929      return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
   2930    }
   2931    function extendLineToColumn(cm, lineNum, column) {
   2932      var endCh = lineLength(cm, lineNum);
   2933      var spaces = new Array(column-endCh+1).join(' ');
   2934      cm.setCursor(Pos(lineNum, endCh));
   2935      cm.replaceRange(spaces, cm.getCursor());
   2936    }
   2937    // This functions selects a rectangular block
   2938    // of text with selectionEnd as any of its corner
   2939    // Height of block:
   2940    // Difference in selectionEnd.line and first/last selection.line
   2941    // Width of the block:
   2942    // Distance between selectionEnd.ch and any(first considered here) selection.ch
   2943    function selectBlock(cm, selectionEnd) {
   2944      var selections = [], ranges = cm.listSelections();
   2945      var head = copyCursor(cm.clipPos(selectionEnd));
   2946      var isClipped = !cursorEqual(selectionEnd, head);
   2947      var curHead = cm.getCursor('head');
   2948      var primIndex = getIndex(ranges, curHead);
   2949      var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor);
   2950      var max = ranges.length - 1;
   2951      var index = max - primIndex > primIndex ? max : 0;
   2952      var base = ranges[index].anchor;
   2953 
   2954      var firstLine = Math.min(base.line, head.line);
   2955      var lastLine = Math.max(base.line, head.line);
   2956      var baseCh = base.ch, headCh = head.ch;
   2957 
   2958      var dir = ranges[index].head.ch - baseCh;
   2959      var newDir = headCh - baseCh;
   2960      if (dir > 0 && newDir <= 0) {
   2961        baseCh++;
   2962        if (!isClipped) { headCh--; }
   2963      } else if (dir < 0 && newDir >= 0) {
   2964        baseCh--;
   2965        if (!wasClipped) { headCh++; }
   2966      } else if (dir < 0 && newDir == -1) {
   2967        baseCh--;
   2968        headCh++;
   2969      }
   2970      for (var line = firstLine; line <= lastLine; line++) {
   2971        var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)};
   2972        selections.push(range);
   2973      }
   2974      cm.setSelections(selections);
   2975      selectionEnd.ch = headCh;
   2976      base.ch = baseCh;
   2977      return base;
   2978    }
   2979    function selectForInsert(cm, head, height) {
   2980      var sel = [];
   2981      for (var i = 0; i < height; i++) {
   2982        var lineHead = offsetCursor(head, i, 0);
   2983        sel.push({anchor: lineHead, head: lineHead});
   2984      }
   2985      cm.setSelections(sel, 0);
   2986    }
   2987    // getIndex returns the index of the cursor in the selections.
   2988    function getIndex(ranges, cursor, end) {
   2989      for (var i = 0; i < ranges.length; i++) {
   2990        var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor);
   2991        var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor);
   2992        if (atAnchor || atHead) {
   2993          return i;
   2994        }
   2995      }
   2996      return -1;
   2997    }
   2998    function getSelectedAreaRange(cm, vim) {
   2999      var lastSelection = vim.lastSelection;
   3000      var getCurrentSelectedAreaRange = function() {
   3001        var selections = cm.listSelections();
   3002        var start =  selections[0];
   3003        var end = selections[selections.length-1];
   3004        var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
   3005        var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
   3006        return [selectionStart, selectionEnd];
   3007      };
   3008      var getLastSelectedAreaRange = function() {
   3009        var selectionStart = cm.getCursor();
   3010        var selectionEnd = cm.getCursor();
   3011        var block = lastSelection.visualBlock;
   3012        if (block) {
   3013          var width = block.width;
   3014          var height = block.height;
   3015          selectionEnd = Pos(selectionStart.line + height, selectionStart.ch + width);
   3016          var selections = [];
   3017          // selectBlock creates a 'proper' rectangular block.
   3018          // We do not want that in all cases, so we manually set selections.
   3019          for (var i = selectionStart.line; i < selectionEnd.line; i++) {
   3020            var anchor = Pos(i, selectionStart.ch);
   3021            var head = Pos(i, selectionEnd.ch);
   3022            var range = {anchor: anchor, head: head};
   3023            selections.push(range);
   3024          }
   3025          cm.setSelections(selections);
   3026        } else {
   3027          var start = lastSelection.anchorMark.find();
   3028          var end = lastSelection.headMark.find();
   3029          var line = end.line - start.line;
   3030          var ch = end.ch - start.ch;
   3031          selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch};
   3032          if (lastSelection.visualLine) {
   3033            selectionStart = Pos(selectionStart.line, 0);
   3034            selectionEnd = Pos(selectionEnd.line, lineLength(cm, selectionEnd.line));
   3035          }
   3036          cm.setSelection(selectionStart, selectionEnd);
   3037        }
   3038        return [selectionStart, selectionEnd];
   3039      };
   3040      if (!vim.visualMode) {
   3041      // In case of replaying the action.
   3042        return getLastSelectedAreaRange();
   3043      } else {
   3044        return getCurrentSelectedAreaRange();
   3045      }
   3046    }
   3047    // Updates the previous selection with the current selection's values. This
   3048    // should only be called in visual mode.
   3049    function updateLastSelection(cm, vim) {
   3050      var anchor = vim.sel.anchor;
   3051      var head = vim.sel.head;
   3052      // To accommodate the effect of lastPastedText in the last selection
   3053      if (vim.lastPastedText) {
   3054        head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length);
   3055        vim.lastPastedText = null;
   3056      }
   3057      vim.lastSelection = {'anchorMark': cm.setBookmark(anchor),
   3058                           'headMark': cm.setBookmark(head),
   3059                           'anchor': copyCursor(anchor),
   3060                           'head': copyCursor(head),
   3061                           'visualMode': vim.visualMode,
   3062                           'visualLine': vim.visualLine,
   3063                           'visualBlock': vim.visualBlock};
   3064    }
   3065    function expandSelection(cm, start, end) {
   3066      var sel = cm.state.vim.sel;
   3067      var head = sel.head;
   3068      var anchor = sel.anchor;
   3069      var tmp;
   3070      if (cursorIsBefore(end, start)) {
   3071        tmp = end;
   3072        end = start;
   3073        start = tmp;
   3074      }
   3075      if (cursorIsBefore(head, anchor)) {
   3076        head = cursorMin(start, head);
   3077        anchor = cursorMax(anchor, end);
   3078      } else {
   3079        anchor = cursorMin(start, anchor);
   3080        head = cursorMax(head, end);
   3081        head = offsetCursor(head, 0, -1);
   3082        if (head.ch == -1 && head.line != cm.firstLine()) {
   3083          head = Pos(head.line - 1, lineLength(cm, head.line - 1));
   3084        }
   3085      }
   3086      return [anchor, head];
   3087    }
   3088    /**
   3089     * Updates the CodeMirror selection to match the provided vim selection.
   3090     * If no arguments are given, it uses the current vim selection state.
   3091     */
   3092    function updateCmSelection(cm, sel, mode) {
   3093      var vim = cm.state.vim;
   3094      sel = sel || vim.sel;
   3095      var mode = mode ||
   3096        vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char';
   3097      var cmSel = makeCmSelection(cm, sel, mode);
   3098      cm.setSelections(cmSel.ranges, cmSel.primary);
   3099      updateFakeCursor(cm);
   3100    }
   3101    function makeCmSelection(cm, sel, mode, exclusive) {
   3102      var head = copyCursor(sel.head);
   3103      var anchor = copyCursor(sel.anchor);
   3104      if (mode == 'char') {
   3105        var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
   3106        var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
   3107        head = offsetCursor(sel.head, 0, headOffset);
   3108        anchor = offsetCursor(sel.anchor, 0, anchorOffset);
   3109        return {
   3110          ranges: [{anchor: anchor, head: head}],
   3111          primary: 0
   3112        };
   3113      } else if (mode == 'line') {
   3114        if (!cursorIsBefore(sel.head, sel.anchor)) {
   3115          anchor.ch = 0;
   3116 
   3117          var lastLine = cm.lastLine();
   3118          if (head.line > lastLine) {
   3119            head.line = lastLine;
   3120          }
   3121          head.ch = lineLength(cm, head.line);
   3122        } else {
   3123          head.ch = 0;
   3124          anchor.ch = lineLength(cm, anchor.line);
   3125        }
   3126        return {
   3127          ranges: [{anchor: anchor, head: head}],
   3128          primary: 0
   3129        };
   3130      } else if (mode == 'block') {
   3131        var top = Math.min(anchor.line, head.line),
   3132            left = Math.min(anchor.ch, head.ch),
   3133            bottom = Math.max(anchor.line, head.line),
   3134            right = Math.max(anchor.ch, head.ch) + 1;
   3135        var height = bottom - top + 1;
   3136        var primary = head.line == top ? 0 : height - 1;
   3137        var ranges = [];
   3138        for (var i = 0; i < height; i++) {
   3139          ranges.push({
   3140            anchor: Pos(top + i, left),
   3141            head: Pos(top + i, right)
   3142          });
   3143        }
   3144        return {
   3145          ranges: ranges,
   3146          primary: primary
   3147        };
   3148      }
   3149    }
   3150    function getHead(cm) {
   3151      var cur = cm.getCursor('head');
   3152      if (cm.getSelection().length == 1) {
   3153        // Small corner case when only 1 character is selected. The "real"
   3154        // head is the left of head and anchor.
   3155        cur = cursorMin(cur, cm.getCursor('anchor'));
   3156      }
   3157      return cur;
   3158    }
   3159 
   3160    /**
   3161     * If moveHead is set to false, the CodeMirror selection will not be
   3162     * touched. The caller assumes the responsibility of putting the cursor
   3163    * in the right place.
   3164     */
   3165    function exitVisualMode(cm, moveHead) {
   3166      var vim = cm.state.vim;
   3167      if (moveHead !== false) {
   3168        cm.setCursor(clipCursorToContent(cm, vim.sel.head));
   3169      }
   3170      updateLastSelection(cm, vim);
   3171      vim.visualMode = false;
   3172      vim.visualLine = false;
   3173      vim.visualBlock = false;
   3174      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
   3175      if (vim.fakeCursor) {
   3176        vim.fakeCursor.clear();
   3177      }
   3178    }
   3179 
   3180    // Remove any trailing newlines from the selection. For
   3181    // example, with the caret at the start of the last word on the line,
   3182    // 'dw' should word, but not the newline, while 'w' should advance the
   3183    // caret to the first character of the next line.
   3184    function clipToLine(cm, curStart, curEnd) {
   3185      var selection = cm.getRange(curStart, curEnd);
   3186      // Only clip if the selection ends with trailing newline + whitespace
   3187      if (/\n\s*$/.test(selection)) {
   3188        var lines = selection.split('\n');
   3189        // We know this is all whitespace.
   3190        lines.pop();
   3191 
   3192        // Cases:
   3193        // 1. Last word is an empty line - do not clip the trailing '\n'
   3194        // 2. Last word is not an empty line - clip the trailing '\n'
   3195        var line;
   3196        // Find the line containing the last word, and clip all whitespace up
   3197        // to it.
   3198        for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
   3199          curEnd.line--;
   3200          curEnd.ch = 0;
   3201        }
   3202        // If the last word is not an empty line, clip an additional newline
   3203        if (line) {
   3204          curEnd.line--;
   3205          curEnd.ch = lineLength(cm, curEnd.line);
   3206        } else {
   3207          curEnd.ch = 0;
   3208        }
   3209      }
   3210    }
   3211 
   3212    // Expand the selection to line ends.
   3213    function expandSelectionToLine(_cm, curStart, curEnd) {
   3214      curStart.ch = 0;
   3215      curEnd.ch = 0;
   3216      curEnd.line++;
   3217    }
   3218 
   3219    function findFirstNonWhiteSpaceCharacter(text) {
   3220      if (!text) {
   3221        return 0;
   3222      }
   3223      var firstNonWS = text.search(/\S/);
   3224      return firstNonWS == -1 ? text.length : firstNonWS;
   3225    }
   3226 
   3227    function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
   3228      var cur = getHead(cm);
   3229      var line = cm.getLine(cur.line);
   3230      var idx = cur.ch;
   3231 
   3232      // Seek to first word or non-whitespace character, depending on if
   3233      // noSymbol is true.
   3234      var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0];
   3235      while (!test(line.charAt(idx))) {
   3236        idx++;
   3237        if (idx >= line.length) { return null; }
   3238      }
   3239 
   3240      if (bigWord) {
   3241        test = bigWordCharTest[0];
   3242      } else {
   3243        test = wordCharTest[0];
   3244        if (!test(line.charAt(idx))) {
   3245          test = wordCharTest[1];
   3246        }
   3247      }
   3248 
   3249      var end = idx, start = idx;
   3250      while (test(line.charAt(end)) && end < line.length) { end++; }
   3251      while (test(line.charAt(start)) && start >= 0) { start--; }
   3252      start++;
   3253 
   3254      if (inclusive) {
   3255        // If present, include all whitespace after word.
   3256        // Otherwise, include all whitespace before word, except indentation.
   3257        var wordEnd = end;
   3258        while (/\s/.test(line.charAt(end)) && end < line.length) { end++; }
   3259        if (wordEnd == end) {
   3260          var wordStart = start;
   3261          while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; }
   3262          if (!start) { start = wordStart; }
   3263        }
   3264      }
   3265      return { start: Pos(cur.line, start), end: Pos(cur.line, end) };
   3266    }
   3267 
   3268    function recordJumpPosition(cm, oldCur, newCur) {
   3269      if (!cursorEqual(oldCur, newCur)) {
   3270        vimGlobalState.jumpList.add(cm, oldCur, newCur);
   3271      }
   3272    }
   3273 
   3274    function recordLastCharacterSearch(increment, args) {
   3275        vimGlobalState.lastCharacterSearch.increment = increment;
   3276        vimGlobalState.lastCharacterSearch.forward = args.forward;
   3277        vimGlobalState.lastCharacterSearch.selectedCharacter = args.selectedCharacter;
   3278    }
   3279 
   3280    var symbolToMode = {
   3281        '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',
   3282        '[': 'section', ']': 'section',
   3283        '*': 'comment', '/': 'comment',
   3284        'm': 'method', 'M': 'method',
   3285        '#': 'preprocess'
   3286    };
   3287    var findSymbolModes = {
   3288      bracket: {
   3289        isComplete: function(state) {
   3290          if (state.nextCh === state.symb) {
   3291            state.depth++;
   3292            if (state.depth >= 1)return true;
   3293          } else if (state.nextCh === state.reverseSymb) {
   3294            state.depth--;
   3295          }
   3296          return false;
   3297        }
   3298      },
   3299      section: {
   3300        init: function(state) {
   3301          state.curMoveThrough = true;
   3302          state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';
   3303        },
   3304        isComplete: function(state) {
   3305          return state.index === 0 && state.nextCh === state.symb;
   3306        }
   3307      },
   3308      comment: {
   3309        isComplete: function(state) {
   3310          var found = state.lastCh === '*' && state.nextCh === '/';
   3311          state.lastCh = state.nextCh;
   3312          return found;
   3313        }
   3314      },
   3315      // TODO: The original Vim implementation only operates on level 1 and 2.
   3316      // The current implementation doesn't check for code block level and
   3317      // therefore it operates on any levels.
   3318      method: {
   3319        init: function(state) {
   3320          state.symb = (state.symb === 'm' ? '{' : '}');
   3321          state.reverseSymb = state.symb === '{' ? '}' : '{';
   3322        },
   3323        isComplete: function(state) {
   3324          if (state.nextCh === state.symb)return true;
   3325          return false;
   3326        }
   3327      },
   3328      preprocess: {
   3329        init: function(state) {
   3330          state.index = 0;
   3331        },
   3332        isComplete: function(state) {
   3333          if (state.nextCh === '#') {
   3334            var token = state.lineText.match(/#(\w+)/)[1];
   3335            if (token === 'endif') {
   3336              if (state.forward && state.depth === 0) {
   3337                return true;
   3338              }
   3339              state.depth++;
   3340            } else if (token === 'if') {
   3341              if (!state.forward && state.depth === 0) {
   3342                return true;
   3343              }
   3344              state.depth--;
   3345            }
   3346            if (token === 'else' && state.depth === 0)return true;
   3347          }
   3348          return false;
   3349        }
   3350      }
   3351    };
   3352    function findSymbol(cm, repeat, forward, symb) {
   3353      var cur = copyCursor(cm.getCursor());
   3354      var increment = forward ? 1 : -1;
   3355      var endLine = forward ? cm.lineCount() : -1;
   3356      var curCh = cur.ch;
   3357      var line = cur.line;
   3358      var lineText = cm.getLine(line);
   3359      var state = {
   3360        lineText: lineText,
   3361        nextCh: lineText.charAt(curCh),
   3362        lastCh: null,
   3363        index: curCh,
   3364        symb: symb,
   3365        reverseSymb: (forward ?  { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
   3366        forward: forward,
   3367        depth: 0,
   3368        curMoveThrough: false
   3369      };
   3370      var mode = symbolToMode[symb];
   3371      if (!mode)return cur;
   3372      var init = findSymbolModes[mode].init;
   3373      var isComplete = findSymbolModes[mode].isComplete;
   3374      if (init) { init(state); }
   3375      while (line !== endLine && repeat) {
   3376        state.index += increment;
   3377        state.nextCh = state.lineText.charAt(state.index);
   3378        if (!state.nextCh) {
   3379          line += increment;
   3380          state.lineText = cm.getLine(line) || '';
   3381          if (increment > 0) {
   3382            state.index = 0;
   3383          } else {
   3384            var lineLen = state.lineText.length;
   3385            state.index = (lineLen > 0) ? (lineLen-1) : 0;
   3386          }
   3387          state.nextCh = state.lineText.charAt(state.index);
   3388        }
   3389        if (isComplete(state)) {
   3390          cur.line = line;
   3391          cur.ch = state.index;
   3392          repeat--;
   3393        }
   3394      }
   3395      if (state.nextCh || state.curMoveThrough) {
   3396        return Pos(line, state.index);
   3397      }
   3398      return cur;
   3399    }
   3400 
   3401    /*
   3402     * Returns the boundaries of the next word. If the cursor in the middle of
   3403     * the word, then returns the boundaries of the current word, starting at
   3404     * the cursor. If the cursor is at the start/end of a word, and we are going
   3405     * forward/backward, respectively, find the boundaries of the next word.
   3406     *
   3407     * @param {CodeMirror} cm CodeMirror object.
   3408     * @param {Cursor} cur The cursor position.
   3409     * @param {boolean} forward True to search forward. False to search
   3410     *     backward.
   3411     * @param {boolean} bigWord True if punctuation count as part of the word.
   3412     *     False if only [a-zA-Z0-9] characters count as part of the word.
   3413     * @param {boolean} emptyLineIsWord True if empty lines should be treated
   3414     *     as words.
   3415     * @return {Object{from:number, to:number, line: number}} The boundaries of
   3416     *     the word, or null if there are no more words.
   3417     */
   3418    function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {
   3419      var lineNum = cur.line;
   3420      var pos = cur.ch;
   3421      var line = cm.getLine(lineNum);
   3422      var dir = forward ? 1 : -1;
   3423      var charTests = bigWord ? bigWordCharTest: wordCharTest;
   3424 
   3425      if (emptyLineIsWord && line == '') {
   3426        lineNum += dir;
   3427        line = cm.getLine(lineNum);
   3428        if (!isLine(cm, lineNum)) {
   3429          return null;
   3430        }
   3431        pos = (forward) ? 0 : line.length;
   3432      }
   3433 
   3434      while (true) {
   3435        if (emptyLineIsWord && line == '') {
   3436          return { from: 0, to: 0, line: lineNum };
   3437        }
   3438        var stop = (dir > 0) ? line.length : -1;
   3439        var wordStart = stop, wordEnd = stop;
   3440        // Find bounds of next word.
   3441        while (pos != stop) {
   3442          var foundWord = false;
   3443          for (var i = 0; i < charTests.length && !foundWord; ++i) {
   3444            if (charTests[i](line.charAt(pos))) {
   3445              wordStart = pos;
   3446              // Advance to end of word.
   3447              while (pos != stop && charTests[i](line.charAt(pos))) {
   3448                pos += dir;
   3449              }
   3450              wordEnd = pos;
   3451              foundWord = wordStart != wordEnd;
   3452              if (wordStart == cur.ch && lineNum == cur.line &&
   3453                  wordEnd == wordStart + dir) {
   3454                // We started at the end of a word. Find the next one.
   3455                continue;
   3456              } else {
   3457                return {
   3458                  from: Math.min(wordStart, wordEnd + 1),
   3459                  to: Math.max(wordStart, wordEnd),
   3460                  line: lineNum };
   3461              }
   3462            }
   3463          }
   3464          if (!foundWord) {
   3465            pos += dir;
   3466          }
   3467        }
   3468        // Advance to next/prev line.
   3469        lineNum += dir;
   3470        if (!isLine(cm, lineNum)) {
   3471          return null;
   3472        }
   3473        line = cm.getLine(lineNum);
   3474        pos = (dir > 0) ? 0 : line.length;
   3475      }
   3476    }
   3477 
   3478    /**
   3479     * @param {CodeMirror} cm CodeMirror object.
   3480     * @param {Pos} cur The position to start from.
   3481     * @param {int} repeat Number of words to move past.
   3482     * @param {boolean} forward True to search forward. False to search
   3483     *     backward.
   3484     * @param {boolean} wordEnd True to move to end of word. False to move to
   3485     *     beginning of word.
   3486     * @param {boolean} bigWord True if punctuation count as part of the word.
   3487     *     False if only alphabet characters count as part of the word.
   3488     * @return {Cursor} The position the cursor should move to.
   3489     */
   3490    function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) {
   3491      var curStart = copyCursor(cur);
   3492      var words = [];
   3493      if (forward && !wordEnd || !forward && wordEnd) {
   3494        repeat++;
   3495      }
   3496      // For 'e', empty lines are not considered words, go figure.
   3497      var emptyLineIsWord = !(forward && wordEnd);
   3498      for (var i = 0; i < repeat; i++) {
   3499        var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);
   3500        if (!word) {
   3501          var eodCh = lineLength(cm, cm.lastLine());
   3502          words.push(forward
   3503              ? {line: cm.lastLine(), from: eodCh, to: eodCh}
   3504              : {line: 0, from: 0, to: 0});
   3505          break;
   3506        }
   3507        words.push(word);
   3508        cur = Pos(word.line, forward ? (word.to - 1) : word.from);
   3509      }
   3510      var shortCircuit = words.length != repeat;
   3511      var firstWord = words[0];
   3512      var lastWord = words.pop();
   3513      if (forward && !wordEnd) {
   3514        // w
   3515        if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {
   3516          // We did not start in the middle of a word. Discard the extra word at the end.
   3517          lastWord = words.pop();
   3518        }
   3519        return Pos(lastWord.line, lastWord.from);
   3520      } else if (forward && wordEnd) {
   3521        return Pos(lastWord.line, lastWord.to - 1);
   3522      } else if (!forward && wordEnd) {
   3523        // ge
   3524        if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {
   3525          // We did not start in the middle of a word. Discard the extra word at the end.
   3526          lastWord = words.pop();
   3527        }
   3528        return Pos(lastWord.line, lastWord.to);
   3529      } else {
   3530        // b
   3531        return Pos(lastWord.line, lastWord.from);
   3532      }
   3533    }
   3534 
   3535    function moveToCharacter(cm, repeat, forward, character) {
   3536      var cur = cm.getCursor();
   3537      var start = cur.ch;
   3538      var idx;
   3539      for (var i = 0; i < repeat; i ++) {
   3540        var line = cm.getLine(cur.line);
   3541        idx = charIdxInLine(start, line, character, forward, true);
   3542        if (idx == -1) {
   3543          return null;
   3544        }
   3545        start = idx;
   3546      }
   3547      return Pos(cm.getCursor().line, idx);
   3548    }
   3549 
   3550    function moveToColumn(cm, repeat) {
   3551      // repeat is always >= 1, so repeat - 1 always corresponds
   3552      // to the column we want to go to.
   3553      var line = cm.getCursor().line;
   3554      return clipCursorToContent(cm, Pos(line, repeat - 1));
   3555    }
   3556 
   3557    function updateMark(cm, vim, markName, pos) {
   3558      if (!inArray(markName, validMarks)) {
   3559        return;
   3560      }
   3561      if (vim.marks[markName]) {
   3562        vim.marks[markName].clear();
   3563      }
   3564      vim.marks[markName] = cm.setBookmark(pos);
   3565    }
   3566 
   3567    function charIdxInLine(start, line, character, forward, includeChar) {
   3568      // Search for char in line.
   3569      // motion_options: {forward, includeChar}
   3570      // If includeChar = true, include it too.
   3571      // If forward = true, search forward, else search backwards.
   3572      // If char is not found on this line, do nothing
   3573      var idx;
   3574      if (forward) {
   3575        idx = line.indexOf(character, start + 1);
   3576        if (idx != -1 && !includeChar) {
   3577          idx -= 1;
   3578        }
   3579      } else {
   3580        idx = line.lastIndexOf(character, start - 1);
   3581        if (idx != -1 && !includeChar) {
   3582          idx += 1;
   3583        }
   3584      }
   3585      return idx;
   3586    }
   3587 
   3588    function findParagraph(cm, head, repeat, dir, inclusive) {
   3589      var line = head.line;
   3590      var min = cm.firstLine();
   3591      var max = cm.lastLine();
   3592      var start, end, i = line;
   3593      function isEmpty(i) { return !cm.getLine(i); }
   3594      function isBoundary(i, dir, any) {
   3595        if (any) { return isEmpty(i) != isEmpty(i + dir); }
   3596        return !isEmpty(i) && isEmpty(i + dir);
   3597      }
   3598      if (dir) {
   3599        while (min <= i && i <= max && repeat > 0) {
   3600          if (isBoundary(i, dir)) { repeat--; }
   3601          i += dir;
   3602        }
   3603        return new Pos(i, 0);
   3604      }
   3605 
   3606      var vim = cm.state.vim;
   3607      if (vim.visualLine && isBoundary(line, 1, true)) {
   3608        var anchor = vim.sel.anchor;
   3609        if (isBoundary(anchor.line, -1, true)) {
   3610          if (!inclusive || anchor.line != line) {
   3611            line += 1;
   3612          }
   3613        }
   3614      }
   3615      var startState = isEmpty(line);
   3616      for (i = line; i <= max && repeat; i++) {
   3617        if (isBoundary(i, 1, true)) {
   3618          if (!inclusive || isEmpty(i) != startState) {
   3619            repeat--;
   3620          }
   3621        }
   3622      }
   3623      end = new Pos(i, 0);
   3624      // select boundary before paragraph for the last one
   3625      if (i > max && !startState) { startState = true; }
   3626      else { inclusive = false; }
   3627      for (i = line; i > min; i--) {
   3628        if (!inclusive || isEmpty(i) == startState || i == line) {
   3629          if (isBoundary(i, -1, true)) { break; }
   3630        }
   3631      }
   3632      start = new Pos(i, 0);
   3633      return { start: start, end: end };
   3634    }
   3635 
   3636    function findSentence(cm, cur, repeat, dir) {
   3637 
   3638      /*
   3639        Takes an index object
   3640        {
   3641          line: the line string,
   3642          ln: line number,
   3643          pos: index in line,
   3644          dir: direction of traversal (-1 or 1)
   3645        }
   3646        and modifies the line, ln, and pos members to represent the
   3647        next valid position or sets them to null if there are
   3648        no more valid positions.
   3649       */
   3650      function nextChar(cm, idx) {
   3651        if (idx.pos + idx.dir < 0 || idx.pos + idx.dir >= idx.line.length) {
   3652          idx.ln += idx.dir;
   3653          if (!isLine(cm, idx.ln)) {
   3654            idx.line = null;
   3655            idx.ln = null;
   3656            idx.pos = null;
   3657            return;
   3658          }
   3659          idx.line = cm.getLine(idx.ln);
   3660          idx.pos = (idx.dir > 0) ? 0 : idx.line.length - 1;
   3661        }
   3662        else {
   3663          idx.pos += idx.dir;
   3664        }
   3665      }
   3666 
   3667      /*
   3668        Performs one iteration of traversal in forward direction
   3669        Returns an index object of the new location
   3670       */
   3671      function forward(cm, ln, pos, dir) {
   3672        var line = cm.getLine(ln);
   3673        var stop = (line === "");
   3674 
   3675        var curr = {
   3676          line: line,
   3677          ln: ln,
   3678          pos: pos,
   3679          dir: dir,
   3680        }
   3681 
   3682        var last_valid = {
   3683          ln: curr.ln,
   3684          pos: curr.pos,
   3685        }
   3686 
   3687        var skip_empty_lines = (curr.line === "");
   3688 
   3689        // Move one step to skip character we start on
   3690        nextChar(cm, curr);
   3691 
   3692        while (curr.line !== null) {
   3693          last_valid.ln = curr.ln;
   3694          last_valid.pos = curr.pos;
   3695 
   3696          if (curr.line === "" && !skip_empty_lines) {
   3697            return { ln: curr.ln, pos: curr.pos, };
   3698          }
   3699          else if (stop && curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) {
   3700            return { ln: curr.ln, pos: curr.pos, };
   3701          }
   3702          else if (isEndOfSentenceSymbol(curr.line[curr.pos])
   3703            && !stop
   3704            && (curr.pos === curr.line.length - 1
   3705              || isWhiteSpaceString(curr.line[curr.pos + 1]))) {
   3706            stop = true;
   3707          }
   3708 
   3709          nextChar(cm, curr);
   3710        }
   3711 
   3712        /*
   3713          Set the position to the last non whitespace character on the last
   3714          valid line in the case that we reach the end of the document.
   3715        */
   3716        var line = cm.getLine(last_valid.ln);
   3717        last_valid.pos = 0;
   3718        for(var i = line.length - 1; i >= 0; --i) {
   3719          if (!isWhiteSpaceString(line[i])) {
   3720            last_valid.pos = i;
   3721            break;
   3722          }
   3723        }
   3724 
   3725        return last_valid;
   3726 
   3727      }
   3728 
   3729      /*
   3730        Performs one iteration of traversal in reverse direction
   3731        Returns an index object of the new location
   3732       */
   3733      function reverse(cm, ln, pos, dir) {
   3734        var line = cm.getLine(ln);
   3735 
   3736        var curr = {
   3737          line: line,
   3738          ln: ln,
   3739          pos: pos,
   3740          dir: dir,
   3741        }
   3742 
   3743        var last_valid = {
   3744          ln: curr.ln,
   3745          pos: null,
   3746        };
   3747 
   3748        var skip_empty_lines = (curr.line === "");
   3749 
   3750        // Move one step to skip character we start on
   3751        nextChar(cm, curr);
   3752 
   3753        while (curr.line !== null) {
   3754 
   3755          if (curr.line === "" && !skip_empty_lines) {
   3756            if (last_valid.pos !== null) {
   3757              return last_valid;
   3758            }
   3759            else {
   3760              return { ln: curr.ln, pos: curr.pos };
   3761            }
   3762          }
   3763          else if (isEndOfSentenceSymbol(curr.line[curr.pos])
   3764              && last_valid.pos !== null
   3765              && !(curr.ln === last_valid.ln && curr.pos + 1 === last_valid.pos)) {
   3766            return last_valid;
   3767          }
   3768          else if (curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) {
   3769            skip_empty_lines = false;
   3770            last_valid = { ln: curr.ln, pos: curr.pos }
   3771          }
   3772 
   3773          nextChar(cm, curr);
   3774        }
   3775 
   3776        /*
   3777          Set the position to the first non whitespace character on the last
   3778          valid line in the case that we reach the beginning of the document.
   3779        */
   3780        var line = cm.getLine(last_valid.ln);
   3781        last_valid.pos = 0;
   3782        for(var i = 0; i < line.length; ++i) {
   3783          if (!isWhiteSpaceString(line[i])) {
   3784            last_valid.pos = i;
   3785            break;
   3786          }
   3787        }
   3788        return last_valid;
   3789      }
   3790 
   3791      var curr_index = {
   3792        ln: cur.line,
   3793        pos: cur.ch,
   3794      };
   3795 
   3796      while (repeat > 0) {
   3797        if (dir < 0) {
   3798          curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir);
   3799        }
   3800        else {
   3801          curr_index = forward(cm, curr_index.ln, curr_index.pos, dir);
   3802        }
   3803        repeat--;
   3804      }
   3805 
   3806      return Pos(curr_index.ln, curr_index.pos);
   3807    }
   3808 
   3809    // TODO: perhaps this finagling of start and end positions belonds
   3810    // in codemirror/replaceRange?
   3811    function selectCompanionObject(cm, head, symb, inclusive) {
   3812      var cur = head, start, end;
   3813 
   3814      var bracketRegexp = ({
   3815        '(': /[()]/, ')': /[()]/,
   3816        '[': /[[\]]/, ']': /[[\]]/,
   3817        '{': /[{}]/, '}': /[{}]/,
   3818        '<': /[<>]/, '>': /[<>]/})[symb];
   3819      var openSym = ({
   3820        '(': '(', ')': '(',
   3821        '[': '[', ']': '[',
   3822        '{': '{', '}': '{',
   3823        '<': '<', '>': '<'})[symb];
   3824      var curChar = cm.getLine(cur.line).charAt(cur.ch);
   3825      // Due to the behavior of scanForBracket, we need to add an offset if the
   3826      // cursor is on a matching open bracket.
   3827      var offset = curChar === openSym ? 1 : 0;
   3828 
   3829      start = cm.scanForBracket(Pos(cur.line, cur.ch + offset), -1, undefined, {'bracketRegex': bracketRegexp});
   3830      end = cm.scanForBracket(Pos(cur.line, cur.ch + offset), 1, undefined, {'bracketRegex': bracketRegexp});
   3831 
   3832      if (!start || !end) {
   3833        return { start: cur, end: cur };
   3834      }
   3835 
   3836      start = start.pos;
   3837      end = end.pos;
   3838 
   3839      if ((start.line == end.line && start.ch > end.ch)
   3840          || (start.line > end.line)) {
   3841        var tmp = start;
   3842        start = end;
   3843        end = tmp;
   3844      }
   3845 
   3846      if (inclusive) {
   3847        end.ch += 1;
   3848      } else {
   3849        start.ch += 1;
   3850      }
   3851 
   3852      return { start: start, end: end };
   3853    }
   3854 
   3855    // Takes in a symbol and a cursor and tries to simulate text objects that
   3856    // have identical opening and closing symbols
   3857    // TODO support across multiple lines
   3858    function findBeginningAndEnd(cm, head, symb, inclusive) {
   3859      var cur = copyCursor(head);
   3860      var line = cm.getLine(cur.line);
   3861      var chars = line.split('');
   3862      var start, end, i, len;
   3863      var firstIndex = chars.indexOf(symb);
   3864 
   3865      // the decision tree is to always look backwards for the beginning first,
   3866      // but if the cursor is in front of the first instance of the symb,
   3867      // then move the cursor forward
   3868      if (cur.ch < firstIndex) {
   3869        cur.ch = firstIndex;
   3870        // Why is this line even here???
   3871        // cm.setCursor(cur.line, firstIndex+1);
   3872      }
   3873      // otherwise if the cursor is currently on the closing symbol
   3874      else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
   3875        end = cur.ch; // assign end to the current cursor
   3876        --cur.ch; // make sure to look backwards
   3877      }
   3878 
   3879      // if we're currently on the symbol, we've got a start
   3880      if (chars[cur.ch] == symb && !end) {
   3881        start = cur.ch + 1; // assign start to ahead of the cursor
   3882      } else {
   3883        // go backwards to find the start
   3884        for (i = cur.ch; i > -1 && !start; i--) {
   3885          if (chars[i] == symb) {
   3886            start = i + 1;
   3887          }
   3888        }
   3889      }
   3890 
   3891      // look forwards for the end symbol
   3892      if (start && !end) {
   3893        for (i = start, len = chars.length; i < len && !end; i++) {
   3894          if (chars[i] == symb) {
   3895            end = i;
   3896          }
   3897        }
   3898      }
   3899 
   3900      // nothing found
   3901      if (!start || !end) {
   3902        return { start: cur, end: cur };
   3903      }
   3904 
   3905      // include the symbols
   3906      if (inclusive) {
   3907        --start; ++end;
   3908      }
   3909 
   3910      return {
   3911        start: Pos(cur.line, start),
   3912        end: Pos(cur.line, end)
   3913      };
   3914    }
   3915 
   3916    // Search functions
   3917    defineOption('pcre', true, 'boolean');
   3918    function SearchState() {}
   3919    SearchState.prototype = {
   3920      getQuery: function() {
   3921        return vimGlobalState.query;
   3922      },
   3923      setQuery: function(query) {
   3924        vimGlobalState.query = query;
   3925      },
   3926      getOverlay: function() {
   3927        return this.searchOverlay;
   3928      },
   3929      setOverlay: function(overlay) {
   3930        this.searchOverlay = overlay;
   3931      },
   3932      isReversed: function() {
   3933        return vimGlobalState.isReversed;
   3934      },
   3935      setReversed: function(reversed) {
   3936        vimGlobalState.isReversed = reversed;
   3937      },
   3938      getScrollbarAnnotate: function() {
   3939        return this.annotate;
   3940      },
   3941      setScrollbarAnnotate: function(annotate) {
   3942        this.annotate = annotate;
   3943      }
   3944    };
   3945    function getSearchState(cm) {
   3946      var vim = cm.state.vim;
   3947      return vim.searchState_ || (vim.searchState_ = new SearchState());
   3948    }
   3949    function dialog(cm, template, shortText, onClose, options) {
   3950      if (cm.openDialog) {
   3951        cm.openDialog(template, onClose, { bottom: true, value: options.value,
   3952            onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp,
   3953            selectValueOnOpen: false});
   3954      }
   3955      else {
   3956        onClose(prompt(shortText, ''));
   3957      }
   3958    }
   3959    function splitBySlash(argString) {
   3960      return splitBySeparator(argString, '/');
   3961    }
   3962 
   3963    function findUnescapedSlashes(argString) {
   3964      return findUnescapedSeparators(argString, '/');
   3965    }
   3966 
   3967    function splitBySeparator(argString, separator) {
   3968      var slashes = findUnescapedSeparators(argString, separator) || [];
   3969      if (!slashes.length) return [];
   3970      var tokens = [];
   3971      // in case of strings like foo/bar
   3972      if (slashes[0] !== 0) return;
   3973      for (var i = 0; i < slashes.length; i++) {
   3974        if (typeof slashes[i] == 'number')
   3975          tokens.push(argString.substring(slashes[i] + 1, slashes[i+1]));
   3976      }
   3977      return tokens;
   3978    }
   3979 
   3980    function findUnescapedSeparators(str, separator) {
   3981      if (!separator)
   3982        separator = '/';
   3983 
   3984      var escapeNextChar = false;
   3985      var slashes = [];
   3986      for (var i = 0; i < str.length; i++) {
   3987        var c = str.charAt(i);
   3988        if (!escapeNextChar && c == separator) {
   3989          slashes.push(i);
   3990        }
   3991        escapeNextChar = !escapeNextChar && (c == '\\');
   3992      }
   3993      return slashes;
   3994    }
   3995 
   3996    // Translates a search string from ex (vim) syntax into javascript form.
   3997    function translateRegex(str) {
   3998      // When these match, add a '\' if unescaped or remove one if escaped.
   3999      var specials = '|(){';
   4000      // Remove, but never add, a '\' for these.
   4001      var unescape = '}';
   4002      var escapeNextChar = false;
   4003      var out = [];
   4004      for (var i = -1; i < str.length; i++) {
   4005        var c = str.charAt(i) || '';
   4006        var n = str.charAt(i+1) || '';
   4007        var specialComesNext = (n && specials.indexOf(n) != -1);
   4008        if (escapeNextChar) {
   4009          if (c !== '\\' || !specialComesNext) {
   4010            out.push(c);
   4011          }
   4012          escapeNextChar = false;
   4013        } else {
   4014          if (c === '\\') {
   4015            escapeNextChar = true;
   4016            // Treat the unescape list as special for removing, but not adding '\'.
   4017            if (n && unescape.indexOf(n) != -1) {
   4018              specialComesNext = true;
   4019            }
   4020            // Not passing this test means removing a '\'.
   4021            if (!specialComesNext || n === '\\') {
   4022              out.push(c);
   4023            }
   4024          } else {
   4025            out.push(c);
   4026            if (specialComesNext && n !== '\\') {
   4027              out.push('\\');
   4028            }
   4029          }
   4030        }
   4031      }
   4032      return out.join('');
   4033    }
   4034 
   4035    // Translates the replace part of a search and replace from ex (vim) syntax into
   4036    // javascript form.  Similar to translateRegex, but additionally fixes back references
   4037    // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'.
   4038    var charUnescapes = {'\\n': '\n', '\\r': '\r', '\\t': '\t'};
   4039    function translateRegexReplace(str) {
   4040      var escapeNextChar = false;
   4041      var out = [];
   4042      for (var i = -1; i < str.length; i++) {
   4043        var c = str.charAt(i) || '';
   4044        var n = str.charAt(i+1) || '';
   4045        if (charUnescapes[c + n]) {
   4046          out.push(charUnescapes[c+n]);
   4047          i++;
   4048        } else if (escapeNextChar) {
   4049          // At any point in the loop, escapeNextChar is true if the previous
   4050          // character was a '\' and was not escaped.
   4051          out.push(c);
   4052          escapeNextChar = false;
   4053        } else {
   4054          if (c === '\\') {
   4055            escapeNextChar = true;
   4056            if ((isNumber(n) || n === '$')) {
   4057              out.push('$');
   4058            } else if (n !== '/' && n !== '\\') {
   4059              out.push('\\');
   4060            }
   4061          } else {
   4062            if (c === '$') {
   4063              out.push('$');
   4064            }
   4065            out.push(c);
   4066            if (n === '/') {
   4067              out.push('\\');
   4068            }
   4069          }
   4070        }
   4071      }
   4072      return out.join('');
   4073    }
   4074 
   4075    // Unescape \ and / in the replace part, for PCRE mode.
   4076    var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t', '\\&':'&'};
   4077    function unescapeRegexReplace(str) {
   4078      var stream = new CodeMirror.StringStream(str);
   4079      var output = [];
   4080      while (!stream.eol()) {
   4081        // Search for \.
   4082        while (stream.peek() && stream.peek() != '\\') {
   4083          output.push(stream.next());
   4084        }
   4085        var matched = false;
   4086        for (var matcher in unescapes) {
   4087          if (stream.match(matcher, true)) {
   4088            matched = true;
   4089            output.push(unescapes[matcher]);
   4090            break;
   4091          }
   4092        }
   4093        if (!matched) {
   4094          // Don't change anything
   4095          output.push(stream.next());
   4096        }
   4097      }
   4098      return output.join('');
   4099    }
   4100 
   4101    /**
   4102     * Extract the regular expression from the query and return a Regexp object.
   4103     * Returns null if the query is blank.
   4104     * If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
   4105     * If smartCase is passed in, and the query contains upper case letters,
   4106     *   then ignoreCase is overridden, and the 'i' flag will not be set.
   4107     * If the query contains the /i in the flag part of the regular expression,
   4108     *   then both ignoreCase and smartCase are ignored, and 'i' will be passed
   4109     *   through to the Regex object.
   4110     */
   4111    function parseQuery(query, ignoreCase, smartCase) {
   4112      // First update the last search register
   4113      var lastSearchRegister = vimGlobalState.registerController.getRegister('/');
   4114      lastSearchRegister.setText(query);
   4115      // Check if the query is already a regex.
   4116      if (query instanceof RegExp) { return query; }
   4117      // First try to extract regex + flags from the input. If no flags found,
   4118      // extract just the regex. IE does not accept flags directly defined in
   4119      // the regex string in the form /regex/flags
   4120      var slashes = findUnescapedSlashes(query);
   4121      var regexPart;
   4122      var forceIgnoreCase;
   4123      if (!slashes.length) {
   4124        // Query looks like 'regexp'
   4125        regexPart = query;
   4126      } else {
   4127        // Query looks like 'regexp/...'
   4128        regexPart = query.substring(0, slashes[0]);
   4129        var flagsPart = query.substring(slashes[0]);
   4130        forceIgnoreCase = (flagsPart.indexOf('i') != -1);
   4131      }
   4132      if (!regexPart) {
   4133        return null;
   4134      }
   4135      if (!getOption('pcre')) {
   4136        regexPart = translateRegex(regexPart);
   4137      }
   4138      if (smartCase) {
   4139        ignoreCase = (/^[^A-Z]*$/).test(regexPart);
   4140      }
   4141      var regexp = new RegExp(regexPart,
   4142          (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
   4143      return regexp;
   4144    }
   4145    function showConfirm(cm, text) {
   4146      if (cm.openNotification) {
   4147        cm.openNotification('<span class="cm5-vim-notification-error">' + text + '</span>',
   4148                            {bottom: true, duration: 5000});
   4149      } else {
   4150        alert(text);
   4151      }
   4152    }
   4153    function makePrompt(cm, prefix, desc) {
   4154      const doc = cm.getWrapperElement().ownerDocument;
   4155      const fragment = doc.createDocumentFragment();
   4156      const promptEl = doc.createElement("span");
   4157      promptEl.classList.add("cm5-vim-prompt");
   4158 
   4159      let inputParent = promptEl;
   4160      if (prefix) {
   4161        const labelEl = doc.createElement("label");
   4162        labelEl.append(doc.createTextNode(prefix));
   4163        promptEl.append(labelEl);
   4164        inputParent = labelEl;
   4165      }
   4166      const inputEl = doc.createElement("input");
   4167      inputParent.append(inputEl);
   4168      fragment.append(promptEl);
   4169 
   4170      if (desc) {
   4171        const descriptionEl = doc.createElement("span");
   4172        descriptionEl.classList.add("cm5-vim-prompt-description");
   4173        descriptionEl.append(doc.createTextNode(desc));
   4174        fragment.append(descriptionEl);
   4175      }
   4176      return fragment;
   4177    }
   4178    var searchPromptDesc = '(Javascript regexp)';
   4179    function showPrompt(cm, options) {
   4180      var shortText = (options.prefix || '') + ' ' + (options.desc || '');
   4181      var prompt = makePrompt(cm, options.prefix, options.desc);
   4182      dialog(cm, prompt, shortText, options.onClose, options);
   4183    }
   4184    function regexEqual(r1, r2) {
   4185      if (r1 instanceof RegExp && r2 instanceof RegExp) {
   4186          var props = ['global', 'multiline', 'ignoreCase', 'source'];
   4187          for (var i = 0; i < props.length; i++) {
   4188              var prop = props[i];
   4189              if (r1[prop] !== r2[prop]) {
   4190                  return false;
   4191              }
   4192          }
   4193          return true;
   4194      }
   4195      return false;
   4196    }
   4197    // Returns true if the query is valid.
   4198    function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
   4199      if (!rawQuery) {
   4200        return;
   4201      }
   4202      var state = getSearchState(cm);
   4203      var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);
   4204      if (!query) {
   4205        return;
   4206      }
   4207      highlightSearchMatches(cm, query);
   4208      if (regexEqual(query, state.getQuery())) {
   4209        return query;
   4210      }
   4211      state.setQuery(query);
   4212      return query;
   4213    }
   4214    function searchOverlay(query) {
   4215      if (query.source.charAt(0) == '^') {
   4216        var matchSol = true;
   4217      }
   4218      return {
   4219        token: function(stream) {
   4220          if (matchSol && !stream.sol()) {
   4221            stream.skipToEnd();
   4222            return;
   4223          }
   4224          var match = stream.match(query, false);
   4225          if (match) {
   4226            if (match[0].length == 0) {
   4227              // Matched empty string, skip to next.
   4228              stream.next();
   4229              return 'searching';
   4230            }
   4231            if (!stream.sol()) {
   4232              // Backtrack 1 to match \b
   4233              stream.backUp(1);
   4234              if (!query.exec(stream.next() + match[0])) {
   4235                stream.next();
   4236                return null;
   4237              }
   4238            }
   4239            stream.match(query);
   4240            return 'searching';
   4241          }
   4242          while (!stream.eol()) {
   4243            stream.next();
   4244            if (stream.match(query, false)) break;
   4245          }
   4246        },
   4247        query: query
   4248      };
   4249    }
   4250    var highlightTimeout = 0;
   4251    function highlightSearchMatches(cm, query) {
   4252      clearTimeout(highlightTimeout);
   4253      highlightTimeout = setTimeout(function() {
   4254        var searchState = getSearchState(cm);
   4255        var overlay = searchState.getOverlay();
   4256        if (!overlay || query != overlay.query) {
   4257          if (overlay) {
   4258            cm.removeOverlay(overlay);
   4259          }
   4260          overlay = searchOverlay(query);
   4261          cm.addOverlay(overlay);
   4262          if (cm.showMatchesOnScrollbar) {
   4263            if (searchState.getScrollbarAnnotate()) {
   4264              searchState.getScrollbarAnnotate().clear();
   4265            }
   4266            searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query));
   4267          }
   4268          searchState.setOverlay(overlay);
   4269        }
   4270      }, 50);
   4271    }
   4272    function findNext(cm, prev, query, repeat) {
   4273      if (repeat === undefined) { repeat = 1; }
   4274      return cm.operation(function() {
   4275        var pos = cm.getCursor();
   4276        var cursor = cm.getSearchCursor(query, pos);
   4277        for (var i = 0; i < repeat; i++) {
   4278          var found = cursor.find(prev);
   4279          if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); }
   4280          if (!found) {
   4281            // SearchCursor may have returned null because it hit EOF, wrap
   4282            // around and try again.
   4283            cursor = cm.getSearchCursor(query,
   4284                (prev) ? Pos(cm.lastLine()) : Pos(cm.firstLine(), 0) );
   4285            if (!cursor.find(prev)) {
   4286              return;
   4287            }
   4288          }
   4289        }
   4290        return cursor.from();
   4291      });
   4292    }
   4293    function clearSearchHighlight(cm) {
   4294      var state = getSearchState(cm);
   4295      cm.removeOverlay(getSearchState(cm).getOverlay());
   4296      state.setOverlay(null);
   4297      if (state.getScrollbarAnnotate()) {
   4298        state.getScrollbarAnnotate().clear();
   4299        state.setScrollbarAnnotate(null);
   4300      }
   4301    }
   4302    /**
   4303     * Check if pos is in the specified range, INCLUSIVE.
   4304     * Range can be specified with 1 or 2 arguments.
   4305     * If the first range argument is an array, treat it as an array of line
   4306     * numbers. Match pos against any of the lines.
   4307     * If the first range argument is a number,
   4308     *   if there is only 1 range argument, check if pos has the same line
   4309     *       number
   4310     *   if there are 2 range arguments, then check if pos is in between the two
   4311     *       range arguments.
   4312     */
   4313    function isInRange(pos, start, end) {
   4314      if (typeof pos != 'number') {
   4315        // Assume it is a cursor position. Get the line number.
   4316        pos = pos.line;
   4317      }
   4318      if (start instanceof Array) {
   4319        return inArray(pos, start);
   4320      } else {
   4321        if (end) {
   4322          return (pos >= start && pos <= end);
   4323        } else {
   4324          return pos == start;
   4325        }
   4326      }
   4327    }
   4328    function getUserVisibleLines(cm) {
   4329      var scrollInfo = cm.getScrollInfo();
   4330      var occludeToleranceTop = 6;
   4331      var occludeToleranceBottom = 10;
   4332      var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
   4333      var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
   4334      var to = cm.coordsChar({left:0, top: bottomY}, 'local');
   4335      return {top: from.line, bottom: to.line};
   4336    }
   4337 
   4338    function getMarkPos(cm, vim, markName) {
   4339      if (markName == '\'') {
   4340        var history = cm.doc.history.done;
   4341        var event = history[history.length - 2];
   4342        return event && event.ranges && event.ranges[0].head;
   4343      } else if (markName == '.') {
   4344        if (cm.doc.history.lastModTime == 0) {
   4345          return  // If no changes, bail out; don't bother to copy or reverse history array.
   4346        } else {
   4347          var changeHistory = cm.doc.history.done.filter(function(el){ if (el.changes !== undefined) { return el } });
   4348          changeHistory.reverse();
   4349          var lastEditPos = changeHistory[0].changes[0].to;
   4350        }
   4351        return lastEditPos;
   4352      }
   4353 
   4354      var mark = vim.marks[markName];
   4355      return mark && mark.find();
   4356    }
   4357 
   4358    var ExCommandDispatcher = function() {
   4359      this.buildCommandMap_();
   4360    };
   4361    ExCommandDispatcher.prototype = {
   4362      processCommand: function(cm, input, opt_params) {
   4363        var that = this;
   4364        cm.operation(function () {
   4365          cm.curOp.isVimOp = true;
   4366          that._processCommand(cm, input, opt_params);
   4367        });
   4368      },
   4369      _processCommand: function(cm, input, opt_params) {
   4370        var vim = cm.state.vim;
   4371        var commandHistoryRegister = vimGlobalState.registerController.getRegister(':');
   4372        var previousCommand = commandHistoryRegister.toString();
   4373        if (vim.visualMode) {
   4374          exitVisualMode(cm);
   4375        }
   4376        var inputStream = new CodeMirror.StringStream(input);
   4377        // update ": with the latest command whether valid or invalid
   4378        commandHistoryRegister.setText(input);
   4379        var params = opt_params || {};
   4380        params.input = input;
   4381        try {
   4382          this.parseInput_(cm, inputStream, params);
   4383        } catch(e) {
   4384          showConfirm(cm, e);
   4385          throw e;
   4386        }
   4387        var command;
   4388        var commandName;
   4389        if (!params.commandName) {
   4390          // If only a line range is defined, move to the line.
   4391          if (params.line !== undefined) {
   4392            commandName = 'move';
   4393          }
   4394        } else {
   4395          command = this.matchCommand_(params.commandName);
   4396          if (command) {
   4397            commandName = command.name;
   4398            if (command.excludeFromCommandHistory) {
   4399              commandHistoryRegister.setText(previousCommand);
   4400            }
   4401            this.parseCommandArgs_(inputStream, params, command);
   4402            if (command.type == 'exToKey') {
   4403              // Handle Ex to Key mapping.
   4404              for (var i = 0; i < command.toKeys.length; i++) {
   4405                CodeMirror.Vim.handleKey(cm, command.toKeys[i], 'mapping');
   4406              }
   4407              return;
   4408            } else if (command.type == 'exToEx') {
   4409              // Handle Ex to Ex mapping.
   4410              this.processCommand(cm, command.toInput);
   4411              return;
   4412            }
   4413          }
   4414        }
   4415        if (!commandName) {
   4416          showConfirm(cm, 'Not an editor command ":' + input + '"');
   4417          return;
   4418        }
   4419        try {
   4420          exCommands[commandName](cm, params);
   4421          // Possibly asynchronous commands (e.g. substitute, which might have a
   4422          // user confirmation), are responsible for calling the callback when
   4423          // done. All others have it taken care of for them here.
   4424          if ((!command || !command.possiblyAsync) && params.callback) {
   4425            params.callback();
   4426          }
   4427        } catch(e) {
   4428          showConfirm(cm, e);
   4429          throw e;
   4430        }
   4431      },
   4432      parseInput_: function(cm, inputStream, result) {
   4433        inputStream.eatWhile(':');
   4434        // Parse range.
   4435        if (inputStream.eat('%')) {
   4436          result.line = cm.firstLine();
   4437          result.lineEnd = cm.lastLine();
   4438        } else {
   4439          result.line = this.parseLineSpec_(cm, inputStream);
   4440          if (result.line !== undefined && inputStream.eat(',')) {
   4441            result.lineEnd = this.parseLineSpec_(cm, inputStream);
   4442          }
   4443        }
   4444 
   4445        // Parse command name.
   4446        var commandMatch = inputStream.match(/^(\w+)/);
   4447        if (commandMatch) {
   4448          result.commandName = commandMatch[1];
   4449        } else {
   4450          result.commandName = inputStream.match(/.*/)[0];
   4451        }
   4452 
   4453        return result;
   4454      },
   4455      parseLineSpec_: function(cm, inputStream) {
   4456        var numberMatch = inputStream.match(/^(\d+)/);
   4457        if (numberMatch) {
   4458          // Absolute line number plus offset (N+M or N-M) is probably a typo,
   4459          // not something the user actually wanted. (NB: vim does allow this.)
   4460          return parseInt(numberMatch[1], 10) - 1;
   4461        }
   4462        switch (inputStream.next()) {
   4463          case '.':
   4464            return this.parseLineSpecOffset_(inputStream, cm.getCursor().line);
   4465          case '$':
   4466            return this.parseLineSpecOffset_(inputStream, cm.lastLine());
   4467          case '\'':
   4468            var markName = inputStream.next();
   4469            var markPos = getMarkPos(cm, cm.state.vim, markName);
   4470            if (!markPos) throw new Error('Mark not set');
   4471            return this.parseLineSpecOffset_(inputStream, markPos.line);
   4472          case '-':
   4473          case '+':
   4474            inputStream.backUp(1);
   4475            // Offset is relative to current line if not otherwise specified.
   4476            return this.parseLineSpecOffset_(inputStream, cm.getCursor().line);
   4477          default:
   4478            inputStream.backUp(1);
   4479            return undefined;
   4480        }
   4481      },
   4482      parseLineSpecOffset_: function(inputStream, line) {
   4483        var offsetMatch = inputStream.match(/^([+-])?(\d+)/);
   4484        if (offsetMatch) {
   4485          var offset = parseInt(offsetMatch[2], 10);
   4486          if (offsetMatch[1] == "-") {
   4487            line -= offset;
   4488          } else {
   4489            line += offset;
   4490          }
   4491        }
   4492        return line;
   4493      },
   4494      parseCommandArgs_: function(inputStream, params, command) {
   4495        if (inputStream.eol()) {
   4496          return;
   4497        }
   4498        params.argString = inputStream.match(/.*/)[0];
   4499        // Parse command-line arguments
   4500        var delim = command.argDelimiter || /\s+/;
   4501        var args = trim(params.argString).split(delim);
   4502        if (args.length && args[0]) {
   4503          params.args = args;
   4504        }
   4505      },
   4506      matchCommand_: function(commandName) {
   4507        // Return the command in the command map that matches the shortest
   4508        // prefix of the passed in command name. The match is guaranteed to be
   4509        // unambiguous if the defaultExCommandMap's shortNames are set up
   4510        // correctly. (see @code{defaultExCommandMap}).
   4511        for (var i = commandName.length; i > 0; i--) {
   4512          var prefix = commandName.substring(0, i);
   4513          if (this.commandMap_[prefix]) {
   4514            var command = this.commandMap_[prefix];
   4515            if (command.name.indexOf(commandName) === 0) {
   4516              return command;
   4517            }
   4518          }
   4519        }
   4520        return null;
   4521      },
   4522      buildCommandMap_: function() {
   4523        this.commandMap_ = {};
   4524        for (var i = 0; i < defaultExCommandMap.length; i++) {
   4525          var command = defaultExCommandMap[i];
   4526          var key = command.shortName || command.name;
   4527          this.commandMap_[key] = command;
   4528        }
   4529      },
   4530      map: function(lhs, rhs, ctx) {
   4531        if (lhs != ':' && lhs.charAt(0) == ':') {
   4532          if (ctx) { throw Error('Mode not supported for ex mappings'); }
   4533          var commandName = lhs.substring(1);
   4534          if (rhs != ':' && rhs.charAt(0) == ':') {
   4535            // Ex to Ex mapping
   4536            this.commandMap_[commandName] = {
   4537              name: commandName,
   4538              type: 'exToEx',
   4539              toInput: rhs.substring(1),
   4540              user: true
   4541            };
   4542          } else {
   4543            // Ex to key mapping
   4544            this.commandMap_[commandName] = {
   4545              name: commandName,
   4546              type: 'exToKey',
   4547              toKeys: rhs,
   4548              user: true
   4549            };
   4550          }
   4551        } else {
   4552          if (rhs != ':' && rhs.charAt(0) == ':') {
   4553            // Key to Ex mapping.
   4554            var mapping = {
   4555              keys: lhs,
   4556              type: 'keyToEx',
   4557              exArgs: { input: rhs.substring(1) }
   4558            };
   4559            if (ctx) { mapping.context = ctx; }
   4560            defaultKeymap.unshift(mapping);
   4561          } else {
   4562            // Key to key mapping
   4563            var mapping = {
   4564              keys: lhs,
   4565              type: 'keyToKey',
   4566              toKeys: rhs
   4567            };
   4568            if (ctx) { mapping.context = ctx; }
   4569            defaultKeymap.unshift(mapping);
   4570          }
   4571        }
   4572      },
   4573      unmap: function(lhs, ctx) {
   4574        if (lhs != ':' && lhs.charAt(0) == ':') {
   4575          // Ex to Ex or Ex to key mapping
   4576          if (ctx) { throw Error('Mode not supported for ex mappings'); }
   4577          var commandName = lhs.substring(1);
   4578          if (this.commandMap_[commandName] && this.commandMap_[commandName].user) {
   4579            delete this.commandMap_[commandName];
   4580            return;
   4581          }
   4582        } else {
   4583          // Key to Ex or key to key mapping
   4584          var keys = lhs;
   4585          for (var i = 0; i < defaultKeymap.length; i++) {
   4586            if (keys == defaultKeymap[i].keys
   4587                && defaultKeymap[i].context === ctx) {
   4588              defaultKeymap.splice(i, 1);
   4589              return;
   4590            }
   4591          }
   4592        }
   4593        throw Error('No such mapping.');
   4594      }
   4595    };
   4596 
   4597    var exCommands = {
   4598      colorscheme: function(cm, params) {
   4599        if (!params.args || params.args.length < 1) {
   4600          showConfirm(cm, cm.getOption('theme'));
   4601          return;
   4602        }
   4603        cm.setOption('theme', params.args[0]);
   4604      },
   4605      map: function(cm, params, ctx) {
   4606        var mapArgs = params.args;
   4607        if (!mapArgs || mapArgs.length < 2) {
   4608          if (cm) {
   4609            showConfirm(cm, 'Invalid mapping: ' + params.input);
   4610          }
   4611          return;
   4612        }
   4613        exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx);
   4614      },
   4615      imap: function(cm, params) { this.map(cm, params, 'insert'); },
   4616      nmap: function(cm, params) { this.map(cm, params, 'normal'); },
   4617      vmap: function(cm, params) { this.map(cm, params, 'visual'); },
   4618      unmap: function(cm, params, ctx) {
   4619        var mapArgs = params.args;
   4620        if (!mapArgs || mapArgs.length < 1) {
   4621          if (cm) {
   4622            showConfirm(cm, 'No such mapping: ' + params.input);
   4623          }
   4624          return;
   4625        }
   4626        exCommandDispatcher.unmap(mapArgs[0], ctx);
   4627      },
   4628      move: function(cm, params) {
   4629        commandDispatcher.processCommand(cm, cm.state.vim, {
   4630            type: 'motion',
   4631            motion: 'moveToLineOrEdgeOfDocument',
   4632            motionArgs: { forward: false, explicitRepeat: true,
   4633              linewise: true },
   4634            repeatOverride: params.line+1});
   4635      },
   4636      set: function(cm, params) {
   4637        var setArgs = params.args;
   4638        // Options passed through to the setOption/getOption calls. May be passed in by the
   4639        // local/global versions of the set command
   4640        var setCfg = params.setCfg || {};
   4641        if (!setArgs || setArgs.length < 1) {
   4642          if (cm) {
   4643            showConfirm(cm, 'Invalid mapping: ' + params.input);
   4644          }
   4645          return;
   4646        }
   4647        var expr = setArgs[0].split('=');
   4648        var optionName = expr[0];
   4649        var value = expr[1];
   4650        var forceGet = false;
   4651 
   4652        if (optionName.charAt(optionName.length - 1) == '?') {
   4653          // If post-fixed with ?, then the set is actually a get.
   4654          if (value) { throw Error('Trailing characters: ' + params.argString); }
   4655          optionName = optionName.substring(0, optionName.length - 1);
   4656          forceGet = true;
   4657        }
   4658        if (value === undefined && optionName.substring(0, 2) == 'no') {
   4659          // To set boolean options to false, the option name is prefixed with
   4660          // 'no'.
   4661          optionName = optionName.substring(2);
   4662          value = false;
   4663        }
   4664 
   4665        var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean';
   4666        if (optionIsBoolean && value == undefined) {
   4667          // Calling set with a boolean option sets it to true.
   4668          value = true;
   4669        }
   4670        // If no value is provided, then we assume this is a get.
   4671        if (!optionIsBoolean && value === undefined || forceGet) {
   4672          var oldValue = getOption(optionName, cm, setCfg);
   4673          if (oldValue instanceof Error) {
   4674            showConfirm(cm, oldValue.message);
   4675          } else if (oldValue === true || oldValue === false) {
   4676            showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName);
   4677          } else {
   4678            showConfirm(cm, '  ' + optionName + '=' + oldValue);
   4679          }
   4680        } else {
   4681          var setOptionReturn = setOption(optionName, value, cm, setCfg);
   4682          if (setOptionReturn instanceof Error) {
   4683            showConfirm(cm, setOptionReturn.message);
   4684          }
   4685        }
   4686      },
   4687      setlocal: function (cm, params) {
   4688        // setCfg is passed through to setOption
   4689        params.setCfg = {scope: 'local'};
   4690        this.set(cm, params);
   4691      },
   4692      setglobal: function (cm, params) {
   4693        // setCfg is passed through to setOption
   4694        params.setCfg = {scope: 'global'};
   4695        this.set(cm, params);
   4696      },
   4697      registers: function(cm, params) {
   4698        var regArgs = params.args;
   4699        var registers = vimGlobalState.registerController.registers;
   4700        var regInfo = '----------Registers----------<br><br>';
   4701        if (!regArgs) {
   4702          for (var registerName in registers) {
   4703            var text = registers[registerName].toString();
   4704            if (text.length) {
   4705              regInfo += '"' + registerName + '    ' + text + '<br>';
   4706            }
   4707          }
   4708        } else {
   4709          var registerName;
   4710          regArgs = regArgs.join('');
   4711          for (var i = 0; i < regArgs.length; i++) {
   4712            registerName = regArgs.charAt(i);
   4713            if (!vimGlobalState.registerController.isValidRegister(registerName)) {
   4714              continue;
   4715            }
   4716            var register = registers[registerName] || new Register();
   4717            regInfo += '"' + registerName + '    ' + register.toString() + '<br>';
   4718          }
   4719        }
   4720        showConfirm(cm, regInfo);
   4721      },
   4722      sort: function(cm, params) {
   4723        var reverse, ignoreCase, unique, number, pattern;
   4724        function parseArgs() {
   4725          if (params.argString) {
   4726            var args = new CodeMirror.StringStream(params.argString);
   4727            if (args.eat('!')) { reverse = true; }
   4728            if (args.eol()) { return; }
   4729            if (!args.eatSpace()) { return 'Invalid arguments'; }
   4730            var opts = args.match(/([dinuox]+)?\s*(\/.+\/)?\s*/);
   4731            if (!opts && !args.eol()) { return 'Invalid arguments'; }
   4732            if (opts[1]) {
   4733              ignoreCase = opts[1].indexOf('i') != -1;
   4734              unique = opts[1].indexOf('u') != -1;
   4735              var decimal = opts[1].indexOf('d') != -1 || opts[1].indexOf('n') != -1 && 1;
   4736              var hex = opts[1].indexOf('x') != -1 && 1;
   4737              var octal = opts[1].indexOf('o') != -1 && 1;
   4738              if (decimal + hex + octal > 1) { return 'Invalid arguments'; }
   4739              number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
   4740            }
   4741            if (opts[2]) {
   4742              pattern = new RegExp(opts[2].substr(1, opts[2].length - 2), ignoreCase ? 'i' : '');
   4743            }
   4744          }
   4745        }
   4746        var err = parseArgs();
   4747        if (err) {
   4748          showConfirm(cm, err + ': ' + params.argString);
   4749          return;
   4750        }
   4751        var lineStart = params.line || cm.firstLine();
   4752        var lineEnd = params.lineEnd || params.line || cm.lastLine();
   4753        if (lineStart == lineEnd) { return; }
   4754        var curStart = Pos(lineStart, 0);
   4755        var curEnd = Pos(lineEnd, lineLength(cm, lineEnd));
   4756        var text = cm.getRange(curStart, curEnd).split('\n');
   4757        var numberRegex = pattern ? pattern :
   4758           (number == 'decimal') ? /(-?)([\d]+)/ :
   4759           (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :
   4760           (number == 'octal') ? /([0-7]+)/ : null;
   4761        var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;
   4762        var numPart = [], textPart = [];
   4763        if (number || pattern) {
   4764          for (var i = 0; i < text.length; i++) {
   4765            var matchPart = pattern ? text[i].match(pattern) : null;
   4766            if (matchPart && matchPart[0] != '') {
   4767              numPart.push(matchPart);
   4768            } else if (!pattern && numberRegex.exec(text[i])) {
   4769              numPart.push(text[i]);
   4770            } else {
   4771              textPart.push(text[i]);
   4772            }
   4773          }
   4774        } else {
   4775          textPart = text;
   4776        }
   4777        function compareFn(a, b) {
   4778          if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
   4779          if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }
   4780          var anum = number && numberRegex.exec(a);
   4781          var bnum = number && numberRegex.exec(b);
   4782          if (!anum) { return a < b ? -1 : 1; }
   4783          anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);
   4784          bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);
   4785          return anum - bnum;
   4786        }
   4787        function comparePatternFn(a, b) {
   4788          if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
   4789          if (ignoreCase) { a[0] = a[0].toLowerCase(); b[0] = b[0].toLowerCase(); }
   4790          return (a[0] < b[0]) ? -1 : 1;
   4791        }
   4792        numPart.sort(pattern ? comparePatternFn : compareFn);
   4793        if (pattern) {
   4794          for (var i = 0; i < numPart.length; i++) {
   4795            numPart[i] = numPart[i].input;
   4796          }
   4797        } else if (!number) { textPart.sort(compareFn); }
   4798        text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);
   4799        if (unique) { // Remove duplicate lines
   4800          var textOld = text;
   4801          var lastLine;
   4802          text = [];
   4803          for (var i = 0; i < textOld.length; i++) {
   4804            if (textOld[i] != lastLine) {
   4805              text.push(textOld[i]);
   4806            }
   4807            lastLine = textOld[i];
   4808          }
   4809        }
   4810        cm.replaceRange(text.join('\n'), curStart, curEnd);
   4811      },
   4812      global: function(cm, params) {
   4813        // a global command is of the form
   4814        // :[range]g/pattern/[cmd]
   4815        // argString holds the string /pattern/[cmd]
   4816        var argString = params.argString;
   4817        if (!argString) {
   4818          showConfirm(cm, 'Regular Expression missing from global');
   4819          return;
   4820        }
   4821        // range is specified here
   4822        var lineStart = (params.line !== undefined) ? params.line : cm.firstLine();
   4823        var lineEnd = params.lineEnd || params.line || cm.lastLine();
   4824        // get the tokens from argString
   4825        var tokens = splitBySlash(argString);
   4826        var regexPart = argString, cmd;
   4827        if (tokens.length) {
   4828          regexPart = tokens[0];
   4829          cmd = tokens.slice(1, tokens.length).join('/');
   4830        }
   4831        if (regexPart) {
   4832          // If regex part is empty, then use the previous query. Otherwise
   4833          // use the regex part as the new query.
   4834          try {
   4835           updateSearchQuery(cm, regexPart, true /** ignoreCase */,
   4836             true /** smartCase */);
   4837          } catch (e) {
   4838           showConfirm(cm, 'Invalid regex: ' + regexPart);
   4839           return;
   4840          }
   4841        }
   4842        // now that we have the regexPart, search for regex matches in the
   4843        // specified range of lines
   4844        var query = getSearchState(cm).getQuery();
   4845        var matchedLines = [], content = '';
   4846        for (var i = lineStart; i <= lineEnd; i++) {
   4847          var matched = query.test(cm.getLine(i));
   4848          if (matched) {
   4849            matchedLines.push(i+1);
   4850            content+= cm.getLine(i) + '<br>';
   4851          }
   4852        }
   4853        // if there is no [cmd], just display the list of matched lines
   4854        if (!cmd) {
   4855          showConfirm(cm, content);
   4856          return;
   4857        }
   4858        var index = 0;
   4859        var nextCommand = function() {
   4860          if (index < matchedLines.length) {
   4861            var command = matchedLines[index] + cmd;
   4862            exCommandDispatcher.processCommand(cm, command, {
   4863              callback: nextCommand
   4864            });
   4865          }
   4866          index++;
   4867        };
   4868        nextCommand();
   4869      },
   4870      substitute: function(cm, params) {
   4871        if (!cm.getSearchCursor) {
   4872          throw new Error('Search feature not available. Requires searchcursor.js or ' +
   4873              'any other getSearchCursor implementation.');
   4874        }
   4875        var argString = params.argString;
   4876        var tokens = argString ? splitBySeparator(argString, argString[0]) : [];
   4877        var regexPart, replacePart = '', trailing, flagsPart, count;
   4878        var confirm = false; // Whether to confirm each replace.
   4879        var global = false; // True to replace all instances on a line, false to replace only 1.
   4880        if (tokens.length) {
   4881          regexPart = tokens[0];
   4882          if (getOption('pcre') && regexPart !== '') {
   4883              regexPart = new RegExp(regexPart).source; //normalize not escaped characters
   4884          }
   4885          replacePart = tokens[1];
   4886          if (regexPart && regexPart[regexPart.length - 1] === '$') {
   4887            regexPart = regexPart.slice(0, regexPart.length - 1) + '\\n';
   4888            replacePart = replacePart ? replacePart + '\n' : '\n';
   4889          }
   4890          if (replacePart !== undefined) {
   4891            if (getOption('pcre')) {
   4892              replacePart = unescapeRegexReplace(replacePart.replace(/([^\\])&/g,"$1$$&"));
   4893            } else {
   4894              replacePart = translateRegexReplace(replacePart);
   4895            }
   4896            vimGlobalState.lastSubstituteReplacePart = replacePart;
   4897          }
   4898          trailing = tokens[2] ? tokens[2].split(' ') : [];
   4899        } else {
   4900          // either the argString is empty or its of the form ' hello/world'
   4901          // actually splitBySlash returns a list of tokens
   4902          // only if the string starts with a '/'
   4903          if (argString && argString.length) {
   4904            showConfirm(cm, 'Substitutions should be of the form ' +
   4905                ':s/pattern/replace/');
   4906            return;
   4907          }
   4908        }
   4909        // After the 3rd slash, we can have flags followed by a space followed
   4910        // by count.
   4911        if (trailing) {
   4912          flagsPart = trailing[0];
   4913          count = parseInt(trailing[1]);
   4914          if (flagsPart) {
   4915            if (flagsPart.indexOf('c') != -1) {
   4916              confirm = true;
   4917              flagsPart.replace('c', '');
   4918            }
   4919            if (flagsPart.indexOf('g') != -1) {
   4920              global = true;
   4921              flagsPart.replace('g', '');
   4922            }
   4923            if (getOption('pcre')) {
   4924               regexPart = regexPart + '/' + flagsPart;
   4925            } else {
   4926               regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart;
   4927            }
   4928          }
   4929        }
   4930        if (regexPart) {
   4931          // If regex part is empty, then use the previous query. Otherwise use
   4932          // the regex part as the new query.
   4933          try {
   4934            updateSearchQuery(cm, regexPart, true /** ignoreCase */,
   4935              true /** smartCase */);
   4936          } catch (e) {
   4937            showConfirm(cm, 'Invalid regex: ' + regexPart);
   4938            return;
   4939          }
   4940        }
   4941        replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart;
   4942        if (replacePart === undefined) {
   4943          showConfirm(cm, 'No previous substitute regular expression');
   4944          return;
   4945        }
   4946        var state = getSearchState(cm);
   4947        var query = state.getQuery();
   4948        var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
   4949        var lineEnd = params.lineEnd || lineStart;
   4950        if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) {
   4951          lineEnd = Infinity;
   4952        }
   4953        if (count) {
   4954          lineStart = lineEnd;
   4955          lineEnd = lineStart + count - 1;
   4956        }
   4957        var startPos = clipCursorToContent(cm, Pos(lineStart, 0));
   4958        var cursor = cm.getSearchCursor(query, startPos);
   4959        doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback);
   4960      },
   4961      redo: CodeMirror.commands.redo,
   4962      undo: CodeMirror.commands.undo,
   4963      write: function(cm) {
   4964        if (CodeMirror.commands.save) {
   4965          // If a save command is defined, call it.
   4966          CodeMirror.commands.save(cm);
   4967        } else if (cm.save) {
   4968          // Saves to text area if no save command is defined and cm.save() is available.
   4969          cm.save();
   4970        }
   4971      },
   4972      nohlsearch: function(cm) {
   4973        clearSearchHighlight(cm);
   4974      },
   4975      yank: function (cm) {
   4976        var cur = copyCursor(cm.getCursor());
   4977        var line = cur.line;
   4978        var lineText = cm.getLine(line);
   4979        vimGlobalState.registerController.pushText(
   4980          '0', 'yank', lineText, true, true);
   4981      },
   4982      delmarks: function(cm, params) {
   4983        if (!params.argString || !trim(params.argString)) {
   4984          showConfirm(cm, 'Argument required');
   4985          return;
   4986        }
   4987 
   4988        var state = cm.state.vim;
   4989        var stream = new CodeMirror.StringStream(trim(params.argString));
   4990        while (!stream.eol()) {
   4991          stream.eatSpace();
   4992 
   4993          // Record the streams position at the beginning of the loop for use
   4994          // in error messages.
   4995          var count = stream.pos;
   4996 
   4997          if (!stream.match(/[a-zA-Z]/, false)) {
   4998            showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
   4999            return;
   5000          }
   5001 
   5002          var sym = stream.next();
   5003          // Check if this symbol is part of a range
   5004          if (stream.match('-', true)) {
   5005            // This symbol is part of a range.
   5006 
   5007            // The range must terminate at an alphabetic character.
   5008            if (!stream.match(/[a-zA-Z]/, false)) {
   5009              showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
   5010              return;
   5011            }
   5012 
   5013            var startMark = sym;
   5014            var finishMark = stream.next();
   5015            // The range must terminate at an alphabetic character which
   5016            // shares the same case as the start of the range.
   5017            if (isLowerCase(startMark) && isLowerCase(finishMark) ||
   5018                isUpperCase(startMark) && isUpperCase(finishMark)) {
   5019              var start = startMark.charCodeAt(0);
   5020              var finish = finishMark.charCodeAt(0);
   5021              if (start >= finish) {
   5022                showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
   5023                return;
   5024              }
   5025 
   5026              // Because marks are always ASCII values, and we have
   5027              // determined that they are the same case, we can use
   5028              // their char codes to iterate through the defined range.
   5029              for (var j = 0; j <= finish - start; j++) {
   5030                var mark = String.fromCharCode(start + j);
   5031                delete state.marks[mark];
   5032              }
   5033            } else {
   5034              showConfirm(cm, 'Invalid argument: ' + startMark + '-');
   5035              return;
   5036            }
   5037          } else {
   5038            // This symbol is a valid mark, and is not part of a range.
   5039            delete state.marks[sym];
   5040          }
   5041        }
   5042      }
   5043    };
   5044 
   5045    var exCommandDispatcher = new ExCommandDispatcher();
   5046 
   5047    /**
   5048    * @param {CodeMirror} cm CodeMirror instance we are in.
   5049    * @param {boolean} confirm Whether to confirm each replace.
   5050    * @param {Cursor} lineStart Line to start replacing from.
   5051    * @param {Cursor} lineEnd Line to stop replacing at.
   5052    * @param {RegExp} query Query for performing matches with.
   5053    * @param {string} replaceWith Text to replace matches with. May contain $1,
   5054    *     $2, etc for replacing captured groups using Javascript replace.
   5055    * @param {function()} callback A callback for when the replace is done.
   5056    */
   5057    function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query,
   5058        replaceWith, callback) {
   5059      // Set up all the functions.
   5060      cm.state.vim.exMode = true;
   5061      var done = false;
   5062      var lastPos = searchCursor.from();
   5063      function replaceAll() {
   5064        cm.operation(function() {
   5065          while (!done) {
   5066            replace();
   5067            next();
   5068          }
   5069          stop();
   5070        });
   5071      }
   5072      function replace() {
   5073        var text = cm.getRange(searchCursor.from(), searchCursor.to());
   5074        var newText = text.replace(query, replaceWith);
   5075        searchCursor.replace(newText);
   5076      }
   5077      function next() {
   5078        // The below only loops to skip over multiple occurrences on the same
   5079        // line when 'global' is not true.
   5080        while(searchCursor.findNext() &&
   5081              isInRange(searchCursor.from(), lineStart, lineEnd)) {
   5082          if (!global && lastPos && searchCursor.from().line == lastPos.line) {
   5083            continue;
   5084          }
   5085          cm.scrollIntoView(searchCursor.from(), 30);
   5086          cm.setSelection(searchCursor.from(), searchCursor.to());
   5087          lastPos = searchCursor.from();
   5088          done = false;
   5089          return;
   5090        }
   5091        done = true;
   5092      }
   5093      function stop(close) {
   5094        if (close) { close(); }
   5095        cm.focus();
   5096        if (lastPos) {
   5097          cm.setCursor(lastPos);
   5098          var vim = cm.state.vim;
   5099          vim.exMode = false;
   5100          vim.lastHPos = vim.lastHSPos = lastPos.ch;
   5101        }
   5102        if (callback) { callback(); }
   5103      }
   5104      function onPromptKeyDown(e, _value, close) {
   5105        // Swallow all keys.
   5106        CodeMirror.e_stop(e);
   5107        var keyName = CodeMirror.keyName(e);
   5108        switch (keyName) {
   5109          case 'Y':
   5110            replace(); next(); break;
   5111          case 'N':
   5112            next(); break;
   5113          case 'A':
   5114            // replaceAll contains a call to close of its own. We don't want it
   5115            // to fire too early or multiple times.
   5116            var savedCallback = callback;
   5117            callback = undefined;
   5118            cm.operation(replaceAll);
   5119            callback = savedCallback;
   5120            break;
   5121          case 'L':
   5122            replace();
   5123            // fall through and exit.
   5124          case 'Q':
   5125          case 'Esc':
   5126          case 'Ctrl-C':
   5127          case 'Ctrl-[':
   5128            stop(close);
   5129            break;
   5130        }
   5131        if (done) { stop(close); }
   5132        return true;
   5133      }
   5134 
   5135      // Actually do replace.
   5136      next();
   5137      if (done) {
   5138        showConfirm(cm, 'No matches for ' + query.source);
   5139        return;
   5140      }
   5141      if (!confirm) {
   5142        replaceAll();
   5143        if (callback) { callback(); }
   5144        return;
   5145      }
   5146      showPrompt(cm, {
   5147        prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)',
   5148        onKeyDown: onPromptKeyDown
   5149      });
   5150    }
   5151 
   5152    CodeMirror.keyMap.vim = {
   5153      attach: attachVimMap,
   5154      detach: detachVimMap,
   5155      call: cmKey
   5156    };
   5157 
   5158    function exitInsertMode(cm) {
   5159      var vim = cm.state.vim;
   5160      var macroModeState = vimGlobalState.macroModeState;
   5161      var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');
   5162      var isPlaying = macroModeState.isPlaying;
   5163      var lastChange = macroModeState.lastInsertModeChanges;
   5164      if (!isPlaying) {
   5165        cm.off('change', onChange);
   5166        CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
   5167      }
   5168      if (!isPlaying && vim.insertModeRepeat > 1) {
   5169        // Perform insert mode repeat for commands like 3,a and 3,o.
   5170        repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
   5171            true /** repeatForInsert */);
   5172        vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;
   5173      }
   5174      delete vim.insertModeRepeat;
   5175      vim.insertMode = false;
   5176      cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1);
   5177      cm.setOption('keyMap', 'vim');
   5178      cm.setOption('disableInput', true);
   5179      cm.toggleOverwrite(false); // exit replace mode if we were in it.
   5180      // update the ". register before exiting insert mode
   5181      insertModeChangeRegister.setText(lastChange.changes.join(''));
   5182      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
   5183      if (macroModeState.isRecording) {
   5184        logInsertModeChange(macroModeState);
   5185      }
   5186    }
   5187 
   5188    function _mapCommand(command) {
   5189      defaultKeymap.unshift(command);
   5190    }
   5191 
   5192    function mapCommand(keys, type, name, args, extra) {
   5193      var command = {keys: keys, type: type};
   5194      command[type] = name;
   5195      command[type + "Args"] = args;
   5196      for (var key in extra)
   5197        command[key] = extra[key];
   5198      _mapCommand(command);
   5199    }
   5200 
   5201    // The timeout in milliseconds for the two-character ESC keymap should be
   5202    // adjusted according to your typing speed to prevent false positives.
   5203    defineOption('insertModeEscKeysTimeout', 200, 'number');
   5204 
   5205    CodeMirror.keyMap['vim-insert'] = {
   5206      // TODO: override navigation keys so that Esc will cancel automatic
   5207      // indentation from o, O, i_<CR>
   5208      fallthrough: ['default'],
   5209      attach: attachVimMap,
   5210      detach: detachVimMap,
   5211      call: cmKey
   5212    };
   5213 
   5214    CodeMirror.keyMap['vim-replace'] = {
   5215      'Backspace': 'goCharLeft',
   5216      fallthrough: ['vim-insert'],
   5217      attach: attachVimMap,
   5218      detach: detachVimMap,
   5219      call: cmKey
   5220    };
   5221 
   5222    function executeMacroRegister(cm, vim, macroModeState, registerName) {
   5223      var register = vimGlobalState.registerController.getRegister(registerName);
   5224      if (registerName == ':') {
   5225        // Read-only register containing last Ex command.
   5226        if (register.keyBuffer[0]) {
   5227          exCommandDispatcher.processCommand(cm, register.keyBuffer[0]);
   5228        }
   5229        macroModeState.isPlaying = false;
   5230        return;
   5231      }
   5232      var keyBuffer = register.keyBuffer;
   5233      var imc = 0;
   5234      macroModeState.isPlaying = true;
   5235      macroModeState.replaySearchQueries = register.searchQueries.slice(0);
   5236      for (var i = 0; i < keyBuffer.length; i++) {
   5237        var text = keyBuffer[i];
   5238        var match, key;
   5239        while (text) {
   5240          // Pull off one command key, which is either a single character
   5241          // or a special sequence wrapped in '<' and '>', e.g. '<Space>'.
   5242          match = (/<\w+-.+?>|<\w+>|./).exec(text);
   5243          key = match[0];
   5244          text = text.substring(match.index + key.length);
   5245          CodeMirror.Vim.handleKey(cm, key, 'macro');
   5246          if (vim.insertMode) {
   5247            var changes = register.insertModeChanges[imc++].changes;
   5248            vimGlobalState.macroModeState.lastInsertModeChanges.changes =
   5249                changes;
   5250            repeatInsertModeChanges(cm, changes, 1);
   5251            exitInsertMode(cm);
   5252          }
   5253        }
   5254      }
   5255      macroModeState.isPlaying = false;
   5256    }
   5257 
   5258    function logKey(macroModeState, key) {
   5259      if (macroModeState.isPlaying) { return; }
   5260      var registerName = macroModeState.latestRegister;
   5261      var register = vimGlobalState.registerController.getRegister(registerName);
   5262      if (register) {
   5263        register.pushText(key);
   5264      }
   5265    }
   5266 
   5267    function logInsertModeChange(macroModeState) {
   5268      if (macroModeState.isPlaying) { return; }
   5269      var registerName = macroModeState.latestRegister;
   5270      var register = vimGlobalState.registerController.getRegister(registerName);
   5271      if (register && register.pushInsertModeChanges) {
   5272        register.pushInsertModeChanges(macroModeState.lastInsertModeChanges);
   5273      }
   5274    }
   5275 
   5276    function logSearchQuery(macroModeState, query) {
   5277      if (macroModeState.isPlaying) { return; }
   5278      var registerName = macroModeState.latestRegister;
   5279      var register = vimGlobalState.registerController.getRegister(registerName);
   5280      if (register && register.pushSearchQuery) {
   5281        register.pushSearchQuery(query);
   5282      }
   5283    }
   5284 
   5285    /**
   5286     * Listens for changes made in insert mode.
   5287     * Should only be active in insert mode.
   5288     */
   5289    function onChange(cm, changeObj) {
   5290      var macroModeState = vimGlobalState.macroModeState;
   5291      var lastChange = macroModeState.lastInsertModeChanges;
   5292      if (!macroModeState.isPlaying) {
   5293        while(changeObj) {
   5294          lastChange.expectCursorActivityForChange = true;
   5295          if (lastChange.ignoreCount > 1) {
   5296            lastChange.ignoreCount--;
   5297          } else if (changeObj.origin == '+input' || changeObj.origin == 'paste'
   5298              || changeObj.origin === undefined /* only in testing */) {
   5299            var selectionCount = cm.listSelections().length;
   5300            if (selectionCount > 1)
   5301              lastChange.ignoreCount = selectionCount;
   5302            var text = changeObj.text.join('\n');
   5303            if (lastChange.maybeReset) {
   5304              lastChange.changes = [];
   5305              lastChange.maybeReset = false;
   5306            }
   5307            if (text) {
   5308              if (cm.state.overwrite && !/\n/.test(text)) {
   5309                lastChange.changes.push([text]);
   5310              } else {
   5311                lastChange.changes.push(text);
   5312              }
   5313            }
   5314          }
   5315          // Change objects may be chained with next.
   5316          changeObj = changeObj.next;
   5317        }
   5318      }
   5319    }
   5320 
   5321    /**
   5322    * Listens for any kind of cursor activity on CodeMirror.
   5323    */
   5324    function onCursorActivity(cm) {
   5325      var vim = cm.state.vim;
   5326      if (vim.insertMode) {
   5327        // Tracking cursor activity in insert mode (for macro support).
   5328        var macroModeState = vimGlobalState.macroModeState;
   5329        if (macroModeState.isPlaying) { return; }
   5330        var lastChange = macroModeState.lastInsertModeChanges;
   5331        if (lastChange.expectCursorActivityForChange) {
   5332          lastChange.expectCursorActivityForChange = false;
   5333        } else {
   5334          // Cursor moved outside the context of an edit. Reset the change.
   5335          lastChange.maybeReset = true;
   5336        }
   5337      } else if (!cm.curOp.isVimOp) {
   5338        handleExternalSelection(cm, vim);
   5339      }
   5340      if (vim.visualMode) {
   5341        updateFakeCursor(cm);
   5342      }
   5343    }
   5344    function updateFakeCursor(cm) {
   5345      var vim = cm.state.vim;
   5346      var from = clipCursorToContent(cm, copyCursor(vim.sel.head));
   5347      var to = offsetCursor(from, 0, 1);
   5348      if (vim.fakeCursor) {
   5349        vim.fakeCursor.clear();
   5350      }
   5351      vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'});
   5352    }
   5353    function handleExternalSelection(cm, vim) {
   5354      var anchor = cm.getCursor('anchor');
   5355      var head = cm.getCursor('head');
   5356      // Enter or exit visual mode to match mouse selection.
   5357      if (vim.visualMode && !cm.somethingSelected()) {
   5358        exitVisualMode(cm, false);
   5359      } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
   5360        vim.visualMode = true;
   5361        vim.visualLine = false;
   5362        CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
   5363      }
   5364      if (vim.visualMode) {
   5365        // Bind CodeMirror selection model to vim selection model.
   5366        // Mouse selections are considered visual characterwise.
   5367        var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0;
   5368        var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0;
   5369        head = offsetCursor(head, 0, headOffset);
   5370        anchor = offsetCursor(anchor, 0, anchorOffset);
   5371        vim.sel = {
   5372          anchor: anchor,
   5373          head: head
   5374        };
   5375        updateMark(cm, vim, '<', cursorMin(head, anchor));
   5376        updateMark(cm, vim, '>', cursorMax(head, anchor));
   5377      } else if (!vim.insertMode) {
   5378        // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse.
   5379        vim.lastHPos = cm.getCursor().ch;
   5380      }
   5381    }
   5382 
   5383    /** Wrapper for special keys pressed in insert mode */
   5384    function InsertModeKey(keyName) {
   5385      this.keyName = keyName;
   5386    }
   5387 
   5388    /**
   5389    * Handles raw key down events from the text area.
   5390    * - Should only be active in insert mode.
   5391    * - For recording deletes in insert mode.
   5392    */
   5393    function onKeyEventTargetKeyDown(e) {
   5394      var macroModeState = vimGlobalState.macroModeState;
   5395      var lastChange = macroModeState.lastInsertModeChanges;
   5396      var keyName = CodeMirror.keyName(e);
   5397      if (!keyName) { return; }
   5398      function onKeyFound() {
   5399        if (lastChange.maybeReset) {
   5400          lastChange.changes = [];
   5401          lastChange.maybeReset = false;
   5402        }
   5403        lastChange.changes.push(new InsertModeKey(keyName));
   5404        return true;
   5405      }
   5406      if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
   5407        CodeMirror.lookupKey(keyName, 'vim-insert', onKeyFound);
   5408      }
   5409    }
   5410 
   5411    /**
   5412     * Repeats the last edit, which includes exactly 1 command and at most 1
   5413     * insert. Operator and motion commands are read from lastEditInputState,
   5414     * while action commands are read from lastEditActionCommand.
   5415     *
   5416     * If repeatForInsert is true, then the function was called by
   5417     * exitInsertMode to repeat the insert mode changes the user just made. The
   5418     * corresponding enterInsertMode call was made with a count.
   5419     */
   5420    function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
   5421      var macroModeState = vimGlobalState.macroModeState;
   5422      macroModeState.isPlaying = true;
   5423      var isAction = !!vim.lastEditActionCommand;
   5424      var cachedInputState = vim.inputState;
   5425      function repeatCommand() {
   5426        if (isAction) {
   5427          commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);
   5428        } else {
   5429          commandDispatcher.evalInput(cm, vim);
   5430        }
   5431      }
   5432      function repeatInsert(repeat) {
   5433        if (macroModeState.lastInsertModeChanges.changes.length > 0) {
   5434          // For some reason, repeat cw in desktop VIM does not repeat
   5435          // insert mode changes. Will conform to that behavior.
   5436          repeat = !vim.lastEditActionCommand ? 1 : repeat;
   5437          var changeObject = macroModeState.lastInsertModeChanges;
   5438          repeatInsertModeChanges(cm, changeObject.changes, repeat);
   5439        }
   5440      }
   5441      vim.inputState = vim.lastEditInputState;
   5442      if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {
   5443        // o and O repeat have to be interlaced with insert repeats so that the
   5444        // insertions appear on separate lines instead of the last line.
   5445        for (var i = 0; i < repeat; i++) {
   5446          repeatCommand();
   5447          repeatInsert(1);
   5448        }
   5449      } else {
   5450        if (!repeatForInsert) {
   5451          // Hack to get the cursor to end up at the right place. If I is
   5452          // repeated in insert mode repeat, cursor will be 1 insert
   5453          // change set left of where it should be.
   5454          repeatCommand();
   5455        }
   5456        repeatInsert(repeat);
   5457      }
   5458      vim.inputState = cachedInputState;
   5459      if (vim.insertMode && !repeatForInsert) {
   5460        // Don't exit insert mode twice. If repeatForInsert is set, then we
   5461        // were called by an exitInsertMode call lower on the stack.
   5462        exitInsertMode(cm);
   5463      }
   5464      macroModeState.isPlaying = false;
   5465    }
   5466 
   5467    function repeatInsertModeChanges(cm, changes, repeat) {
   5468      function keyHandler(binding) {
   5469        if (typeof binding == 'string') {
   5470          CodeMirror.commands[binding](cm);
   5471        } else {
   5472          binding(cm);
   5473        }
   5474        return true;
   5475      }
   5476      var head = cm.getCursor('head');
   5477      var visualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.visualBlock;
   5478      if (visualBlock) {
   5479        // Set up block selection again for repeating the changes.
   5480        selectForInsert(cm, head, visualBlock + 1);
   5481        repeat = cm.listSelections().length;
   5482        cm.setCursor(head);
   5483      }
   5484      for (var i = 0; i < repeat; i++) {
   5485        if (visualBlock) {
   5486          cm.setCursor(offsetCursor(head, i, 0));
   5487        }
   5488        for (var j = 0; j < changes.length; j++) {
   5489          var change = changes[j];
   5490          if (change instanceof InsertModeKey) {
   5491            CodeMirror.lookupKey(change.keyName, 'vim-insert', keyHandler);
   5492          } else if (typeof change == "string") {
   5493            var cur = cm.getCursor();
   5494            cm.replaceRange(change, cur, cur);
   5495          } else {
   5496            var start = cm.getCursor();
   5497            var end = offsetCursor(start, 0, change[0].length);
   5498            cm.replaceRange(change[0], start, end);
   5499          }
   5500        }
   5501      }
   5502      if (visualBlock) {
   5503        cm.setCursor(offsetCursor(head, 0, 1));
   5504      }
   5505    }
   5506 
   5507    resetVimGlobalState();
   5508    return vimApi;
   5509  };
   5510  // Initialize Vim and make it available as an API.
   5511  CodeMirror.Vim = Vim();
   5512 });