[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/media/vendor/codemirror/keymap/ -> vim.js (source)

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


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer