browser/devtools/sourceeditor/codemirror/keymap/vim.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/devtools/sourceeditor/codemirror/keymap/vim.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,4123 @@
     1.4 +/**
     1.5 + * Supported keybindings:
     1.6 + *
     1.7 + *   Motion:
     1.8 + *   h, j, k, l
     1.9 + *   gj, gk
    1.10 + *   e, E, w, W, b, B, ge, gE
    1.11 + *   f<character>, F<character>, t<character>, T<character>
    1.12 + *   $, ^, 0, -, +, _
    1.13 + *   gg, G
    1.14 + *   %
    1.15 + *   '<character>, `<character>
    1.16 + *
    1.17 + *   Operator:
    1.18 + *   d, y, c
    1.19 + *   dd, yy, cc
    1.20 + *   g~, g~g~
    1.21 + *   >, <, >>, <<
    1.22 + *
    1.23 + *   Operator-Motion:
    1.24 + *   x, X, D, Y, C, ~
    1.25 + *
    1.26 + *   Action:
    1.27 + *   a, i, s, A, I, S, o, O
    1.28 + *   zz, z., z<CR>, zt, zb, z-
    1.29 + *   J
    1.30 + *   u, Ctrl-r
    1.31 + *   m<character>
    1.32 + *   r<character>
    1.33 + *
    1.34 + *   Modes:
    1.35 + *   ESC - leave insert mode, visual mode, and clear input state.
    1.36 + *   Ctrl-[, Ctrl-c - same as ESC.
    1.37 + *
    1.38 + * Registers: unnamed, -, a-z, A-Z, 0-9
    1.39 + *   (Does not respect the special case for number registers when delete
    1.40 + *    operator is made with these commands: %, (, ),  , /, ?, n, N, {, } )
    1.41 + *   TODO: Implement the remaining registers.
    1.42 + * Marks: a-z, A-Z, and 0-9
    1.43 + *   TODO: Implement the remaining special marks. They have more complex
    1.44 + *       behavior.
    1.45 + *
    1.46 + * Events:
    1.47 + *  'vim-mode-change' - raised on the editor anytime the current mode changes,
    1.48 + *                      Event object: {mode: "visual", subMode: "linewise"}
    1.49 + *
    1.50 + * Code structure:
    1.51 + *  1. Default keymap
    1.52 + *  2. Variable declarations and short basic helpers
    1.53 + *  3. Instance (External API) implementation
    1.54 + *  4. Internal state tracking objects (input state, counter) implementation
    1.55 + *     and instanstiation
    1.56 + *  5. Key handler (the main command dispatcher) implementation
    1.57 + *  6. Motion, operator, and action implementations
    1.58 + *  7. Helper functions for the key handler, motions, operators, and actions
    1.59 + *  8. Set up Vim to work as a keymap for CodeMirror.
    1.60 + */
    1.61 +
    1.62 +(function(mod) {
    1.63 +  if (typeof exports == "object" && typeof module == "object") // CommonJS
    1.64 +    mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog"));
    1.65 +  else if (typeof define == "function" && define.amd) // AMD
    1.66 +    define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog"], mod);
    1.67 +  else // Plain browser env
    1.68 +    mod(CodeMirror);
    1.69 +})(function(CodeMirror) {
    1.70 +  'use strict';
    1.71 +
    1.72 +  var defaultKeymap = [
    1.73 +    // Key to key mapping. This goes first to make it possible to override
    1.74 +    // existing mappings.
    1.75 +    { keys: ['<Left>'], type: 'keyToKey', toKeys: ['h'] },
    1.76 +    { keys: ['<Right>'], type: 'keyToKey', toKeys: ['l'] },
    1.77 +    { keys: ['<Up>'], type: 'keyToKey', toKeys: ['k'] },
    1.78 +    { keys: ['<Down>'], type: 'keyToKey', toKeys: ['j'] },
    1.79 +    { keys: ['<Space>'], type: 'keyToKey', toKeys: ['l'] },
    1.80 +    { keys: ['<BS>'], type: 'keyToKey', toKeys: ['h'] },
    1.81 +    { keys: ['<C-Space>'], type: 'keyToKey', toKeys: ['W'] },
    1.82 +    { keys: ['<C-BS>'], type: 'keyToKey', toKeys: ['B'] },
    1.83 +    { keys: ['<S-Space>'], type: 'keyToKey', toKeys: ['w'] },
    1.84 +    { keys: ['<S-BS>'], type: 'keyToKey', toKeys: ['b'] },
    1.85 +    { keys: ['<C-n>'], type: 'keyToKey', toKeys: ['j'] },
    1.86 +    { keys: ['<C-p>'], type: 'keyToKey', toKeys: ['k'] },
    1.87 +    { keys: ['<C-[>'], type: 'keyToKey', toKeys: ['<Esc>'] },
    1.88 +    { keys: ['<C-c>'], type: 'keyToKey', toKeys: ['<Esc>'] },
    1.89 +    { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'], context: 'normal' },
    1.90 +    { keys: ['s'], type: 'keyToKey', toKeys: ['x', 'i'], context: 'visual'},
    1.91 +    { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'], context: 'normal' },
    1.92 +    { keys: ['S'], type: 'keyToKey', toKeys: ['d', 'c', 'c'], context: 'visual' },
    1.93 +    { keys: ['<Home>'], type: 'keyToKey', toKeys: ['0'] },
    1.94 +    { keys: ['<End>'], type: 'keyToKey', toKeys: ['$'] },
    1.95 +    { keys: ['<PageUp>'], type: 'keyToKey', toKeys: ['<C-b>'] },
    1.96 +    { keys: ['<PageDown>'], type: 'keyToKey', toKeys: ['<C-f>'] },
    1.97 +    { keys: ['<CR>'], type: 'keyToKey', toKeys: ['j', '^'], context: 'normal' },
    1.98 +    // Motions
    1.99 +    { keys: ['H'], type: 'motion',
   1.100 +        motion: 'moveToTopLine',
   1.101 +        motionArgs: { linewise: true, toJumplist: true }},
   1.102 +    { keys: ['M'], type: 'motion',
   1.103 +        motion: 'moveToMiddleLine',
   1.104 +        motionArgs: { linewise: true, toJumplist: true }},
   1.105 +    { keys: ['L'], type: 'motion',
   1.106 +        motion: 'moveToBottomLine',
   1.107 +        motionArgs: { linewise: true, toJumplist: true }},
   1.108 +    { keys: ['h'], type: 'motion',
   1.109 +        motion: 'moveByCharacters',
   1.110 +        motionArgs: { forward: false }},
   1.111 +    { keys: ['l'], type: 'motion',
   1.112 +        motion: 'moveByCharacters',
   1.113 +        motionArgs: { forward: true }},
   1.114 +    { keys: ['j'], type: 'motion',
   1.115 +        motion: 'moveByLines',
   1.116 +        motionArgs: { forward: true, linewise: true }},
   1.117 +    { keys: ['k'], type: 'motion',
   1.118 +        motion: 'moveByLines',
   1.119 +        motionArgs: { forward: false, linewise: true }},
   1.120 +    { keys: ['g','j'], type: 'motion',
   1.121 +        motion: 'moveByDisplayLines',
   1.122 +        motionArgs: { forward: true }},
   1.123 +    { keys: ['g','k'], type: 'motion',
   1.124 +        motion: 'moveByDisplayLines',
   1.125 +        motionArgs: { forward: false }},
   1.126 +    { keys: ['w'], type: 'motion',
   1.127 +        motion: 'moveByWords',
   1.128 +        motionArgs: { forward: true, wordEnd: false }},
   1.129 +    { keys: ['W'], type: 'motion',
   1.130 +        motion: 'moveByWords',
   1.131 +        motionArgs: { forward: true, wordEnd: false, bigWord: true }},
   1.132 +    { keys: ['e'], type: 'motion',
   1.133 +        motion: 'moveByWords',
   1.134 +        motionArgs: { forward: true, wordEnd: true, inclusive: true }},
   1.135 +    { keys: ['E'], type: 'motion',
   1.136 +        motion: 'moveByWords',
   1.137 +        motionArgs: { forward: true, wordEnd: true, bigWord: true,
   1.138 +            inclusive: true }},
   1.139 +    { keys: ['b'], type: 'motion',
   1.140 +        motion: 'moveByWords',
   1.141 +        motionArgs: { forward: false, wordEnd: false }},
   1.142 +    { keys: ['B'], type: 'motion',
   1.143 +        motion: 'moveByWords',
   1.144 +        motionArgs: { forward: false, wordEnd: false, bigWord: true }},
   1.145 +    { keys: ['g', 'e'], type: 'motion',
   1.146 +        motion: 'moveByWords',
   1.147 +        motionArgs: { forward: false, wordEnd: true, inclusive: true }},
   1.148 +    { keys: ['g', 'E'], type: 'motion',
   1.149 +        motion: 'moveByWords',
   1.150 +        motionArgs: { forward: false, wordEnd: true, bigWord: true,
   1.151 +            inclusive: true }},
   1.152 +    { keys: ['{'], type: 'motion', motion: 'moveByParagraph',
   1.153 +        motionArgs: { forward: false, toJumplist: true }},
   1.154 +    { keys: ['}'], type: 'motion', motion: 'moveByParagraph',
   1.155 +        motionArgs: { forward: true, toJumplist: true }},
   1.156 +    { keys: ['<C-f>'], type: 'motion',
   1.157 +        motion: 'moveByPage', motionArgs: { forward: true }},
   1.158 +    { keys: ['<C-b>'], type: 'motion',
   1.159 +        motion: 'moveByPage', motionArgs: { forward: false }},
   1.160 +    { keys: ['<C-d>'], type: 'motion',
   1.161 +        motion: 'moveByScroll',
   1.162 +        motionArgs: { forward: true, explicitRepeat: true }},
   1.163 +    { keys: ['<C-u>'], type: 'motion',
   1.164 +        motion: 'moveByScroll',
   1.165 +        motionArgs: { forward: false, explicitRepeat: true }},
   1.166 +    { keys: ['g', 'g'], type: 'motion',
   1.167 +        motion: 'moveToLineOrEdgeOfDocument',
   1.168 +        motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
   1.169 +    { keys: ['G'], type: 'motion',
   1.170 +        motion: 'moveToLineOrEdgeOfDocument',
   1.171 +        motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
   1.172 +    { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' },
   1.173 +    { keys: ['^'], type: 'motion',
   1.174 +        motion: 'moveToFirstNonWhiteSpaceCharacter' },
   1.175 +    { keys: ['+'], type: 'motion',
   1.176 +        motion: 'moveByLines',
   1.177 +        motionArgs: { forward: true, toFirstChar:true }},
   1.178 +    { keys: ['-'], type: 'motion',
   1.179 +        motion: 'moveByLines',
   1.180 +        motionArgs: { forward: false, toFirstChar:true }},
   1.181 +    { keys: ['_'], type: 'motion',
   1.182 +        motion: 'moveByLines',
   1.183 +        motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
   1.184 +    { keys: ['$'], type: 'motion',
   1.185 +        motion: 'moveToEol',
   1.186 +        motionArgs: { inclusive: true }},
   1.187 +    { keys: ['%'], type: 'motion',
   1.188 +        motion: 'moveToMatchedSymbol',
   1.189 +        motionArgs: { inclusive: true, toJumplist: true }},
   1.190 +    { keys: ['f', 'character'], type: 'motion',
   1.191 +        motion: 'moveToCharacter',
   1.192 +        motionArgs: { forward: true , inclusive: true }},
   1.193 +    { keys: ['F', 'character'], type: 'motion',
   1.194 +        motion: 'moveToCharacter',
   1.195 +        motionArgs: { forward: false }},
   1.196 +    { keys: ['t', 'character'], type: 'motion',
   1.197 +        motion: 'moveTillCharacter',
   1.198 +        motionArgs: { forward: true, inclusive: true }},
   1.199 +    { keys: ['T', 'character'], type: 'motion',
   1.200 +        motion: 'moveTillCharacter',
   1.201 +        motionArgs: { forward: false }},
   1.202 +    { keys: [';'], type: 'motion', motion: 'repeatLastCharacterSearch',
   1.203 +        motionArgs: { forward: true }},
   1.204 +    { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch',
   1.205 +        motionArgs: { forward: false }},
   1.206 +    { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark',
   1.207 +        motionArgs: {toJumplist: true}},
   1.208 +    { keys: ['`', 'character'], type: 'motion', motion: 'goToMark',
   1.209 +        motionArgs: {toJumplist: true}},
   1.210 +    { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
   1.211 +    { keys: ['[', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
   1.212 +    { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
   1.213 +    { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
   1.214 +    { keys: [']', 'character'], type: 'motion',
   1.215 +        motion: 'moveToSymbol',
   1.216 +        motionArgs: { forward: true, toJumplist: true}},
   1.217 +    { keys: ['[', 'character'], type: 'motion',
   1.218 +        motion: 'moveToSymbol',
   1.219 +        motionArgs: { forward: false, toJumplist: true}},
   1.220 +    { keys: ['|'], type: 'motion',
   1.221 +        motion: 'moveToColumn',
   1.222 +        motionArgs: { }},
   1.223 +    { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { },context:'visual'},
   1.224 +    // Operators
   1.225 +    { keys: ['d'], type: 'operator', operator: 'delete' },
   1.226 +    { keys: ['y'], type: 'operator', operator: 'yank' },
   1.227 +    { keys: ['c'], type: 'operator', operator: 'change' },
   1.228 +    { keys: ['>'], type: 'operator', operator: 'indent',
   1.229 +        operatorArgs: { indentRight: true }},
   1.230 +    { keys: ['<'], type: 'operator', operator: 'indent',
   1.231 +        operatorArgs: { indentRight: false }},
   1.232 +    { keys: ['g', '~'], type: 'operator', operator: 'swapcase' },
   1.233 +    { keys: ['n'], type: 'motion', motion: 'findNext',
   1.234 +        motionArgs: { forward: true, toJumplist: true }},
   1.235 +    { keys: ['N'], type: 'motion', motion: 'findNext',
   1.236 +        motionArgs: { forward: false, toJumplist: true }},
   1.237 +    // Operator-Motion dual commands
   1.238 +    { keys: ['x'], type: 'operatorMotion', operator: 'delete',
   1.239 +        motion: 'moveByCharacters', motionArgs: { forward: true },
   1.240 +        operatorMotionArgs: { visualLine: false }},
   1.241 +    { keys: ['X'], type: 'operatorMotion', operator: 'delete',
   1.242 +        motion: 'moveByCharacters', motionArgs: { forward: false },
   1.243 +        operatorMotionArgs: { visualLine: true }},
   1.244 +    { keys: ['D'], type: 'operatorMotion', operator: 'delete',
   1.245 +      motion: 'moveToEol', motionArgs: { inclusive: true },
   1.246 +        operatorMotionArgs: { visualLine: true }},
   1.247 +    { keys: ['Y'], type: 'operatorMotion', operator: 'yank',
   1.248 +        motion: 'moveToEol', motionArgs: { inclusive: true },
   1.249 +        operatorMotionArgs: { visualLine: true }},
   1.250 +    { keys: ['C'], type: 'operatorMotion',
   1.251 +        operator: 'change',
   1.252 +        motion: 'moveToEol', motionArgs: { inclusive: true },
   1.253 +        operatorMotionArgs: { visualLine: true }},
   1.254 +    { keys: ['~'], type: 'operatorMotion',
   1.255 +        operator: 'swapcase', operatorArgs: { shouldMoveCursor: true },
   1.256 +        motion: 'moveByCharacters', motionArgs: { forward: true }},
   1.257 +    // Actions
   1.258 +    { keys: ['<C-i>'], type: 'action', action: 'jumpListWalk',
   1.259 +        actionArgs: { forward: true }},
   1.260 +    { keys: ['<C-o>'], type: 'action', action: 'jumpListWalk',
   1.261 +        actionArgs: { forward: false }},
   1.262 +    { keys: ['<C-e>'], type: 'action',
   1.263 +        action: 'scroll',
   1.264 +        actionArgs: { forward: true, linewise: true }},
   1.265 +    { keys: ['<C-y>'], type: 'action',
   1.266 +        action: 'scroll',
   1.267 +        actionArgs: { forward: false, linewise: true }},
   1.268 +    { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true,
   1.269 +        actionArgs: { insertAt: 'charAfter' }},
   1.270 +    { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true,
   1.271 +        actionArgs: { insertAt: 'eol' }},
   1.272 +    { keys: ['i'], type: 'action', action: 'enterInsertMode', isEdit: true,
   1.273 +        actionArgs: { insertAt: 'inplace' }},
   1.274 +    { keys: ['I'], type: 'action', action: 'enterInsertMode', isEdit: true,
   1.275 +        actionArgs: { insertAt: 'firstNonBlank' }},
   1.276 +    { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode',
   1.277 +        isEdit: true, interlaceInsertRepeat: true,
   1.278 +        actionArgs: { after: true }},
   1.279 +    { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode',
   1.280 +        isEdit: true, interlaceInsertRepeat: true,
   1.281 +        actionArgs: { after: false }},
   1.282 +    { keys: ['v'], type: 'action', action: 'toggleVisualMode' },
   1.283 +    { keys: ['V'], type: 'action', action: 'toggleVisualMode',
   1.284 +        actionArgs: { linewise: true }},
   1.285 +    { keys: ['g', 'v'], type: 'action', action: 'reselectLastSelection' },
   1.286 +    { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true },
   1.287 +    { keys: ['p'], type: 'action', action: 'paste', isEdit: true,
   1.288 +        actionArgs: { after: true, isEdit: true }},
   1.289 +    { keys: ['P'], type: 'action', action: 'paste', isEdit: true,
   1.290 +        actionArgs: { after: false, isEdit: true }},
   1.291 +    { keys: ['r', 'character'], type: 'action', action: 'replace', isEdit: true },
   1.292 +    { keys: ['@', 'character'], type: 'action', action: 'replayMacro' },
   1.293 +    { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' },
   1.294 +    // Handle Replace-mode as a special case of insert mode.
   1.295 +    { keys: ['R'], type: 'action', action: 'enterInsertMode', isEdit: true,
   1.296 +        actionArgs: { replace: true }},
   1.297 +    { keys: ['u'], type: 'action', action: 'undo' },
   1.298 +    { keys: ['<C-r>'], type: 'action', action: 'redo' },
   1.299 +    { keys: ['m', 'character'], type: 'action', action: 'setMark' },
   1.300 +    { keys: ['"', 'character'], type: 'action', action: 'setRegister' },
   1.301 +    { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor',
   1.302 +        actionArgs: { position: 'center' }},
   1.303 +    { keys: ['z', '.'], type: 'action', action: 'scrollToCursor',
   1.304 +        actionArgs: { position: 'center' },
   1.305 +        motion: 'moveToFirstNonWhiteSpaceCharacter' },
   1.306 +    { keys: ['z', 't'], type: 'action', action: 'scrollToCursor',
   1.307 +        actionArgs: { position: 'top' }},
   1.308 +    { keys: ['z', '<CR>'], type: 'action', action: 'scrollToCursor',
   1.309 +        actionArgs: { position: 'top' },
   1.310 +        motion: 'moveToFirstNonWhiteSpaceCharacter' },
   1.311 +    { keys: ['z', '-'], type: 'action', action: 'scrollToCursor',
   1.312 +        actionArgs: { position: 'bottom' }},
   1.313 +    { keys: ['z', 'b'], type: 'action', action: 'scrollToCursor',
   1.314 +        actionArgs: { position: 'bottom' },
   1.315 +        motion: 'moveToFirstNonWhiteSpaceCharacter' },
   1.316 +    { keys: ['.'], type: 'action', action: 'repeatLastEdit' },
   1.317 +    { keys: ['<C-a>'], type: 'action', action: 'incrementNumberToken',
   1.318 +        isEdit: true,
   1.319 +        actionArgs: {increase: true, backtrack: false}},
   1.320 +    { keys: ['<C-x>'], type: 'action', action: 'incrementNumberToken',
   1.321 +        isEdit: true,
   1.322 +        actionArgs: {increase: false, backtrack: false}},
   1.323 +    // Text object motions
   1.324 +    { keys: ['a', 'character'], type: 'motion',
   1.325 +        motion: 'textObjectManipulation' },
   1.326 +    { keys: ['i', 'character'], type: 'motion',
   1.327 +        motion: 'textObjectManipulation',
   1.328 +        motionArgs: { textObjectInner: true }},
   1.329 +    // Search
   1.330 +    { keys: ['/'], type: 'search',
   1.331 +        searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
   1.332 +    { keys: ['?'], type: 'search',
   1.333 +        searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
   1.334 +    { keys: ['*'], type: 'search',
   1.335 +        searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
   1.336 +    { keys: ['#'], type: 'search',
   1.337 +        searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
   1.338 +    // Ex command
   1.339 +    { keys: [':'], type: 'ex' }
   1.340 +  ];
   1.341 +
   1.342 +  var Pos = CodeMirror.Pos;
   1.343 +
   1.344 +  var Vim = function() {
   1.345 +    CodeMirror.defineOption('vimMode', false, function(cm, val) {
   1.346 +      if (val) {
   1.347 +        cm.setOption('keyMap', 'vim');
   1.348 +        cm.setOption('disableInput', true);
   1.349 +        CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
   1.350 +        cm.on('beforeSelectionChange', beforeSelectionChange);
   1.351 +        maybeInitVimState(cm);
   1.352 +        CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
   1.353 +      } else if (cm.state.vim) {
   1.354 +        cm.setOption('keyMap', 'default');
   1.355 +        cm.setOption('disableInput', false);
   1.356 +        cm.off('beforeSelectionChange', beforeSelectionChange);
   1.357 +        CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
   1.358 +        cm.state.vim = null;
   1.359 +      }
   1.360 +    });
   1.361 +    function beforeSelectionChange(cm, obj) {
   1.362 +      var vim = cm.state.vim;
   1.363 +      if (vim.insertMode || vim.exMode) return;
   1.364 +
   1.365 +      var head = obj.ranges[0].head;
   1.366 +      var anchor = obj.ranges[0].anchor;
   1.367 +      if (head.ch && head.ch == cm.doc.getLine(head.line).length) {
   1.368 +        var pos = Pos(head.line, head.ch - 1);
   1.369 +        obj.update([{anchor: cursorEqual(head, anchor) ? pos : anchor,
   1.370 +                     head: pos}]);
   1.371 +      }
   1.372 +    }
   1.373 +    function getOnPasteFn(cm) {
   1.374 +      var vim = cm.state.vim;
   1.375 +      if (!vim.onPasteFn) {
   1.376 +        vim.onPasteFn = function() {
   1.377 +          if (!vim.insertMode) {
   1.378 +            cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
   1.379 +            actions.enterInsertMode(cm, {}, vim);
   1.380 +          }
   1.381 +        };
   1.382 +      }
   1.383 +      return vim.onPasteFn;
   1.384 +    }
   1.385 +
   1.386 +    var numberRegex = /[\d]/;
   1.387 +    var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
   1.388 +    function makeKeyRange(start, size) {
   1.389 +      var keys = [];
   1.390 +      for (var i = start; i < start + size; i++) {
   1.391 +        keys.push(String.fromCharCode(i));
   1.392 +      }
   1.393 +      return keys;
   1.394 +    }
   1.395 +    var upperCaseAlphabet = makeKeyRange(65, 26);
   1.396 +    var lowerCaseAlphabet = makeKeyRange(97, 26);
   1.397 +    var numbers = makeKeyRange(48, 10);
   1.398 +    var specialSymbols = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;"\''.split('');
   1.399 +    var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace',
   1.400 +        'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter'];
   1.401 +    var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
   1.402 +    var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"']);
   1.403 +
   1.404 +    function isLine(cm, line) {
   1.405 +      return line >= cm.firstLine() && line <= cm.lastLine();
   1.406 +    }
   1.407 +    function isLowerCase(k) {
   1.408 +      return (/^[a-z]$/).test(k);
   1.409 +    }
   1.410 +    function isMatchableSymbol(k) {
   1.411 +      return '()[]{}'.indexOf(k) != -1;
   1.412 +    }
   1.413 +    function isNumber(k) {
   1.414 +      return numberRegex.test(k);
   1.415 +    }
   1.416 +    function isUpperCase(k) {
   1.417 +      return (/^[A-Z]$/).test(k);
   1.418 +    }
   1.419 +    function isWhiteSpaceString(k) {
   1.420 +      return (/^\s*$/).test(k);
   1.421 +    }
   1.422 +    function inArray(val, arr) {
   1.423 +      for (var i = 0; i < arr.length; i++) {
   1.424 +        if (arr[i] == val) {
   1.425 +          return true;
   1.426 +        }
   1.427 +      }
   1.428 +      return false;
   1.429 +    }
   1.430 +
   1.431 +    var options = {};
   1.432 +    function defineOption(name, defaultValue, type) {
   1.433 +      if (defaultValue === undefined) { throw Error('defaultValue is required'); }
   1.434 +      if (!type) { type = 'string'; }
   1.435 +      options[name] = {
   1.436 +        type: type,
   1.437 +        defaultValue: defaultValue
   1.438 +      };
   1.439 +      setOption(name, defaultValue);
   1.440 +    }
   1.441 +
   1.442 +    function setOption(name, value) {
   1.443 +      var option = options[name];
   1.444 +      if (!option) {
   1.445 +        throw Error('Unknown option: ' + name);
   1.446 +      }
   1.447 +      if (option.type == 'boolean') {
   1.448 +        if (value && value !== true) {
   1.449 +          throw Error('Invalid argument: ' + name + '=' + value);
   1.450 +        } else if (value !== false) {
   1.451 +          // Boolean options are set to true if value is not defined.
   1.452 +          value = true;
   1.453 +        }
   1.454 +      }
   1.455 +      option.value = option.type == 'boolean' ? !!value : value;
   1.456 +    }
   1.457 +
   1.458 +    function getOption(name) {
   1.459 +      var option = options[name];
   1.460 +      if (!option) {
   1.461 +        throw Error('Unknown option: ' + name);
   1.462 +      }
   1.463 +      return option.value;
   1.464 +    }
   1.465 +
   1.466 +    var createCircularJumpList = function() {
   1.467 +      var size = 100;
   1.468 +      var pointer = -1;
   1.469 +      var head = 0;
   1.470 +      var tail = 0;
   1.471 +      var buffer = new Array(size);
   1.472 +      function add(cm, oldCur, newCur) {
   1.473 +        var current = pointer % size;
   1.474 +        var curMark = buffer[current];
   1.475 +        function useNextSlot(cursor) {
   1.476 +          var next = ++pointer % size;
   1.477 +          var trashMark = buffer[next];
   1.478 +          if (trashMark) {
   1.479 +            trashMark.clear();
   1.480 +          }
   1.481 +          buffer[next] = cm.setBookmark(cursor);
   1.482 +        }
   1.483 +        if (curMark) {
   1.484 +          var markPos = curMark.find();
   1.485 +          // avoid recording redundant cursor position
   1.486 +          if (markPos && !cursorEqual(markPos, oldCur)) {
   1.487 +            useNextSlot(oldCur);
   1.488 +          }
   1.489 +        } else {
   1.490 +          useNextSlot(oldCur);
   1.491 +        }
   1.492 +        useNextSlot(newCur);
   1.493 +        head = pointer;
   1.494 +        tail = pointer - size + 1;
   1.495 +        if (tail < 0) {
   1.496 +          tail = 0;
   1.497 +        }
   1.498 +      }
   1.499 +      function move(cm, offset) {
   1.500 +        pointer += offset;
   1.501 +        if (pointer > head) {
   1.502 +          pointer = head;
   1.503 +        } else if (pointer < tail) {
   1.504 +          pointer = tail;
   1.505 +        }
   1.506 +        var mark = buffer[(size + pointer) % size];
   1.507 +        // skip marks that are temporarily removed from text buffer
   1.508 +        if (mark && !mark.find()) {
   1.509 +          var inc = offset > 0 ? 1 : -1;
   1.510 +          var newCur;
   1.511 +          var oldCur = cm.getCursor();
   1.512 +          do {
   1.513 +            pointer += inc;
   1.514 +            mark = buffer[(size + pointer) % size];
   1.515 +            // skip marks that are the same as current position
   1.516 +            if (mark &&
   1.517 +                (newCur = mark.find()) &&
   1.518 +                !cursorEqual(oldCur, newCur)) {
   1.519 +              break;
   1.520 +            }
   1.521 +          } while (pointer < head && pointer > tail);
   1.522 +        }
   1.523 +        return mark;
   1.524 +      }
   1.525 +      return {
   1.526 +        cachedCursor: undefined, //used for # and * jumps
   1.527 +        add: add,
   1.528 +        move: move
   1.529 +      };
   1.530 +    };
   1.531 +
   1.532 +    // Returns an object to track the changes associated insert mode.  It
   1.533 +    // clones the object that is passed in, or creates an empty object one if
   1.534 +    // none is provided.
   1.535 +    var createInsertModeChanges = function(c) {
   1.536 +      if (c) {
   1.537 +        // Copy construction
   1.538 +        return {
   1.539 +          changes: c.changes,
   1.540 +          expectCursorActivityForChange: c.expectCursorActivityForChange
   1.541 +        };
   1.542 +      }
   1.543 +      return {
   1.544 +        // Change list
   1.545 +        changes: [],
   1.546 +        // Set to true on change, false on cursorActivity.
   1.547 +        expectCursorActivityForChange: false
   1.548 +      };
   1.549 +    };
   1.550 +
   1.551 +    function MacroModeState() {
   1.552 +      this.latestRegister = undefined;
   1.553 +      this.isPlaying = false;
   1.554 +      this.isRecording = false;
   1.555 +      this.onRecordingDone = undefined;
   1.556 +      this.lastInsertModeChanges = createInsertModeChanges();
   1.557 +    }
   1.558 +    MacroModeState.prototype = {
   1.559 +      exitMacroRecordMode: function() {
   1.560 +        var macroModeState = vimGlobalState.macroModeState;
   1.561 +        macroModeState.onRecordingDone(); // close dialog
   1.562 +        macroModeState.onRecordingDone = undefined;
   1.563 +        macroModeState.isRecording = false;
   1.564 +      },
   1.565 +      enterMacroRecordMode: function(cm, registerName) {
   1.566 +        var register =
   1.567 +            vimGlobalState.registerController.getRegister(registerName);
   1.568 +        if (register) {
   1.569 +          register.clear();
   1.570 +          this.latestRegister = registerName;
   1.571 +          this.onRecordingDone = cm.openDialog(
   1.572 +              '(recording)['+registerName+']', null, {bottom:true});
   1.573 +          this.isRecording = true;
   1.574 +        }
   1.575 +      }
   1.576 +    };
   1.577 +
   1.578 +    function maybeInitVimState(cm) {
   1.579 +      if (!cm.state.vim) {
   1.580 +        // Store instance state in the CodeMirror object.
   1.581 +        cm.state.vim = {
   1.582 +          inputState: new InputState(),
   1.583 +          // Vim's input state that triggered the last edit, used to repeat
   1.584 +          // motions and operators with '.'.
   1.585 +          lastEditInputState: undefined,
   1.586 +          // Vim's action command before the last edit, used to repeat actions
   1.587 +          // with '.' and insert mode repeat.
   1.588 +          lastEditActionCommand: undefined,
   1.589 +          // When using jk for navigation, if you move from a longer line to a
   1.590 +          // shorter line, the cursor may clip to the end of the shorter line.
   1.591 +          // If j is pressed again and cursor goes to the next line, the
   1.592 +          // cursor should go back to its horizontal position on the longer
   1.593 +          // line if it can. This is to keep track of the horizontal position.
   1.594 +          lastHPos: -1,
   1.595 +          // Doing the same with screen-position for gj/gk
   1.596 +          lastHSPos: -1,
   1.597 +          // The last motion command run. Cleared if a non-motion command gets
   1.598 +          // executed in between.
   1.599 +          lastMotion: null,
   1.600 +          marks: {},
   1.601 +          insertMode: false,
   1.602 +          // Repeat count for changes made in insert mode, triggered by key
   1.603 +          // sequences like 3,i. Only exists when insertMode is true.
   1.604 +          insertModeRepeat: undefined,
   1.605 +          visualMode: false,
   1.606 +          // If we are in visual line mode. No effect if visualMode is false.
   1.607 +          visualLine: false,
   1.608 +          lastSelection: null
   1.609 +        };
   1.610 +      }
   1.611 +      return cm.state.vim;
   1.612 +    }
   1.613 +    var vimGlobalState;
   1.614 +    function resetVimGlobalState() {
   1.615 +      vimGlobalState = {
   1.616 +        // The current search query.
   1.617 +        searchQuery: null,
   1.618 +        // Whether we are searching backwards.
   1.619 +        searchIsReversed: false,
   1.620 +        jumpList: createCircularJumpList(),
   1.621 +        macroModeState: new MacroModeState,
   1.622 +        // Recording latest f, t, F or T motion command.
   1.623 +        lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
   1.624 +        registerController: new RegisterController({})
   1.625 +      };
   1.626 +      for (var optionName in options) {
   1.627 +        var option = options[optionName];
   1.628 +        option.value = option.defaultValue;
   1.629 +      }
   1.630 +    }
   1.631 +
   1.632 +    var vimApi= {
   1.633 +      buildKeyMap: function() {
   1.634 +        // TODO: Convert keymap into dictionary format for fast lookup.
   1.635 +      },
   1.636 +      // Testing hook, though it might be useful to expose the register
   1.637 +      // controller anyways.
   1.638 +      getRegisterController: function() {
   1.639 +        return vimGlobalState.registerController;
   1.640 +      },
   1.641 +      // Testing hook.
   1.642 +      resetVimGlobalState_: resetVimGlobalState,
   1.643 +
   1.644 +      // Testing hook.
   1.645 +      getVimGlobalState_: function() {
   1.646 +        return vimGlobalState;
   1.647 +      },
   1.648 +
   1.649 +      // Testing hook.
   1.650 +      maybeInitVimState_: maybeInitVimState,
   1.651 +
   1.652 +      InsertModeKey: InsertModeKey,
   1.653 +      map: function(lhs, rhs, ctx) {
   1.654 +        // Add user defined key bindings.
   1.655 +        exCommandDispatcher.map(lhs, rhs, ctx);
   1.656 +      },
   1.657 +      setOption: setOption,
   1.658 +      getOption: getOption,
   1.659 +      defineOption: defineOption,
   1.660 +      defineEx: function(name, prefix, func){
   1.661 +        if (name.indexOf(prefix) !== 0) {
   1.662 +          throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
   1.663 +        }
   1.664 +        exCommands[name]=func;
   1.665 +        exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
   1.666 +      },
   1.667 +      // This is the outermost function called by CodeMirror, after keys have
   1.668 +      // been mapped to their Vim equivalents.
   1.669 +      handleKey: function(cm, key) {
   1.670 +        var command;
   1.671 +        var vim = maybeInitVimState(cm);
   1.672 +        var macroModeState = vimGlobalState.macroModeState;
   1.673 +        if (macroModeState.isRecording) {
   1.674 +          if (key == 'q') {
   1.675 +            macroModeState.exitMacroRecordMode();
   1.676 +            vim.inputState = new InputState();
   1.677 +            return;
   1.678 +          }
   1.679 +        }
   1.680 +        if (key == '<Esc>') {
   1.681 +          // Clear input state and get back to normal mode.
   1.682 +          vim.inputState = new InputState();
   1.683 +          if (vim.visualMode) {
   1.684 +            exitVisualMode(cm);
   1.685 +          }
   1.686 +          return;
   1.687 +        }
   1.688 +        // Enter visual mode when the mouse selects text.
   1.689 +        if (!vim.visualMode &&
   1.690 +            !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) {
   1.691 +          vim.visualMode = true;
   1.692 +          vim.visualLine = false;
   1.693 +          CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
   1.694 +          cm.on('mousedown', exitVisualMode);
   1.695 +        }
   1.696 +        if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) {
   1.697 +          // Have to special case 0 since it's both a motion and a number.
   1.698 +          command = commandDispatcher.matchCommand(key, defaultKeymap, vim);
   1.699 +        }
   1.700 +        if (!command) {
   1.701 +          if (isNumber(key)) {
   1.702 +            // Increment count unless count is 0 and key is 0.
   1.703 +            vim.inputState.pushRepeatDigit(key);
   1.704 +          }
   1.705 +          if (macroModeState.isRecording) {
   1.706 +            logKey(macroModeState, key);
   1.707 +          }
   1.708 +          return;
   1.709 +        }
   1.710 +        if (command.type == 'keyToKey') {
   1.711 +          // TODO: prevent infinite recursion.
   1.712 +          for (var i = 0; i < command.toKeys.length; i++) {
   1.713 +            this.handleKey(cm, command.toKeys[i]);
   1.714 +          }
   1.715 +        } else {
   1.716 +          if (macroModeState.isRecording) {
   1.717 +            logKey(macroModeState, key);
   1.718 +          }
   1.719 +          commandDispatcher.processCommand(cm, vim, command);
   1.720 +        }
   1.721 +      },
   1.722 +      handleEx: function(cm, input) {
   1.723 +        exCommandDispatcher.processCommand(cm, input);
   1.724 +      }
   1.725 +    };
   1.726 +
   1.727 +    // Represents the current input state.
   1.728 +    function InputState() {
   1.729 +      this.prefixRepeat = [];
   1.730 +      this.motionRepeat = [];
   1.731 +
   1.732 +      this.operator = null;
   1.733 +      this.operatorArgs = null;
   1.734 +      this.motion = null;
   1.735 +      this.motionArgs = null;
   1.736 +      this.keyBuffer = []; // For matching multi-key commands.
   1.737 +      this.registerName = null; // Defaults to the unnamed register.
   1.738 +    }
   1.739 +    InputState.prototype.pushRepeatDigit = function(n) {
   1.740 +      if (!this.operator) {
   1.741 +        this.prefixRepeat = this.prefixRepeat.concat(n);
   1.742 +      } else {
   1.743 +        this.motionRepeat = this.motionRepeat.concat(n);
   1.744 +      }
   1.745 +    };
   1.746 +    InputState.prototype.getRepeat = function() {
   1.747 +      var repeat = 0;
   1.748 +      if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {
   1.749 +        repeat = 1;
   1.750 +        if (this.prefixRepeat.length > 0) {
   1.751 +          repeat *= parseInt(this.prefixRepeat.join(''), 10);
   1.752 +        }
   1.753 +        if (this.motionRepeat.length > 0) {
   1.754 +          repeat *= parseInt(this.motionRepeat.join(''), 10);
   1.755 +        }
   1.756 +      }
   1.757 +      return repeat;
   1.758 +    };
   1.759 +
   1.760 +    /*
   1.761 +     * Register stores information about copy and paste registers.  Besides
   1.762 +     * text, a register must store whether it is linewise (i.e., when it is
   1.763 +     * pasted, should it insert itself into a new line, or should the text be
   1.764 +     * inserted at the cursor position.)
   1.765 +     */
   1.766 +    function Register(text, linewise) {
   1.767 +      this.clear();
   1.768 +      this.keyBuffer = [text || ''];
   1.769 +      this.insertModeChanges = [];
   1.770 +      this.linewise = !!linewise;
   1.771 +    }
   1.772 +    Register.prototype = {
   1.773 +      setText: function(text, linewise) {
   1.774 +        this.keyBuffer = [text || ''];
   1.775 +        this.linewise = !!linewise;
   1.776 +      },
   1.777 +      pushText: function(text, linewise) {
   1.778 +        // if this register has ever been set to linewise, use linewise.
   1.779 +        if (linewise || this.linewise) {
   1.780 +          this.keyBuffer.push('\n');
   1.781 +          this.linewise = true;
   1.782 +        }
   1.783 +        this.keyBuffer.push(text);
   1.784 +      },
   1.785 +      pushInsertModeChanges: function(changes) {
   1.786 +        this.insertModeChanges.push(createInsertModeChanges(changes));
   1.787 +      },
   1.788 +      clear: function() {
   1.789 +        this.keyBuffer = [];
   1.790 +        this.insertModeChanges = [];
   1.791 +        this.linewise = false;
   1.792 +      },
   1.793 +      toString: function() {
   1.794 +        return this.keyBuffer.join('');
   1.795 +      }
   1.796 +    };
   1.797 +
   1.798 +    /*
   1.799 +     * vim registers allow you to keep many independent copy and paste buffers.
   1.800 +     * See http://usevim.com/2012/04/13/registers/ for an introduction.
   1.801 +     *
   1.802 +     * RegisterController keeps the state of all the registers.  An initial
   1.803 +     * state may be passed in.  The unnamed register '"' will always be
   1.804 +     * overridden.
   1.805 +     */
   1.806 +    function RegisterController(registers) {
   1.807 +      this.registers = registers;
   1.808 +      this.unnamedRegister = registers['"'] = new Register();
   1.809 +    }
   1.810 +    RegisterController.prototype = {
   1.811 +      pushText: function(registerName, operator, text, linewise) {
   1.812 +        if (linewise && text.charAt(0) == '\n') {
   1.813 +          text = text.slice(1) + '\n';
   1.814 +        }
   1.815 +        if (linewise && text.charAt(text.length - 1) !== '\n'){
   1.816 +          text += '\n';
   1.817 +        }
   1.818 +        // Lowercase and uppercase registers refer to the same register.
   1.819 +        // Uppercase just means append.
   1.820 +        var register = this.isValidRegister(registerName) ?
   1.821 +            this.getRegister(registerName) : null;
   1.822 +        // if no register/an invalid register was specified, things go to the
   1.823 +        // default registers
   1.824 +        if (!register) {
   1.825 +          switch (operator) {
   1.826 +            case 'yank':
   1.827 +              // The 0 register contains the text from the most recent yank.
   1.828 +              this.registers['0'] = new Register(text, linewise);
   1.829 +              break;
   1.830 +            case 'delete':
   1.831 +            case 'change':
   1.832 +              if (text.indexOf('\n') == -1) {
   1.833 +                // Delete less than 1 line. Update the small delete register.
   1.834 +                this.registers['-'] = new Register(text, linewise);
   1.835 +              } else {
   1.836 +                // Shift down the contents of the numbered registers and put the
   1.837 +                // deleted text into register 1.
   1.838 +                this.shiftNumericRegisters_();
   1.839 +                this.registers['1'] = new Register(text, linewise);
   1.840 +              }
   1.841 +              break;
   1.842 +          }
   1.843 +          // Make sure the unnamed register is set to what just happened
   1.844 +          this.unnamedRegister.setText(text, linewise);
   1.845 +          return;
   1.846 +        }
   1.847 +
   1.848 +        // If we've gotten to this point, we've actually specified a register
   1.849 +        var append = isUpperCase(registerName);
   1.850 +        if (append) {
   1.851 +          register.append(text, linewise);
   1.852 +          // The unnamed register always has the same value as the last used
   1.853 +          // register.
   1.854 +          this.unnamedRegister.append(text, linewise);
   1.855 +        } else {
   1.856 +          register.setText(text, linewise);
   1.857 +          this.unnamedRegister.setText(text, linewise);
   1.858 +        }
   1.859 +      },
   1.860 +      // Gets the register named @name.  If one of @name doesn't already exist,
   1.861 +      // create it.  If @name is invalid, return the unnamedRegister.
   1.862 +      getRegister: function(name) {
   1.863 +        if (!this.isValidRegister(name)) {
   1.864 +          return this.unnamedRegister;
   1.865 +        }
   1.866 +        name = name.toLowerCase();
   1.867 +        if (!this.registers[name]) {
   1.868 +          this.registers[name] = new Register();
   1.869 +        }
   1.870 +        return this.registers[name];
   1.871 +      },
   1.872 +      isValidRegister: function(name) {
   1.873 +        return name && inArray(name, validRegisters);
   1.874 +      },
   1.875 +      shiftNumericRegisters_: function() {
   1.876 +        for (var i = 9; i >= 2; i--) {
   1.877 +          this.registers[i] = this.getRegister('' + (i - 1));
   1.878 +        }
   1.879 +      }
   1.880 +    };
   1.881 +
   1.882 +    var commandDispatcher = {
   1.883 +      matchCommand: function(key, keyMap, vim) {
   1.884 +        var inputState = vim.inputState;
   1.885 +        var keys = inputState.keyBuffer.concat(key);
   1.886 +        var matchedCommands = [];
   1.887 +        var selectedCharacter;
   1.888 +        for (var i = 0; i < keyMap.length; i++) {
   1.889 +          var command = keyMap[i];
   1.890 +          if (matchKeysPartial(keys, command.keys)) {
   1.891 +            if (inputState.operator && command.type == 'action') {
   1.892 +              // Ignore matched action commands after an operator. Operators
   1.893 +              // only operate on motions. This check is really for text
   1.894 +              // objects since aW, a[ etcs conflicts with a.
   1.895 +              continue;
   1.896 +            }
   1.897 +            // Match commands that take <character> as an argument.
   1.898 +            if (command.keys[keys.length - 1] == 'character') {
   1.899 +              selectedCharacter = keys[keys.length - 1];
   1.900 +              if (selectedCharacter.length>1){
   1.901 +                switch(selectedCharacter){
   1.902 +                  case '<CR>':
   1.903 +                    selectedCharacter='\n';
   1.904 +                    break;
   1.905 +                  case '<Space>':
   1.906 +                    selectedCharacter=' ';
   1.907 +                    break;
   1.908 +                  default:
   1.909 +                    continue;
   1.910 +                }
   1.911 +              }
   1.912 +            }
   1.913 +            // Add the command to the list of matched commands. Choose the best
   1.914 +            // command later.
   1.915 +            matchedCommands.push(command);
   1.916 +          }
   1.917 +        }
   1.918 +
   1.919 +        // Returns the command if it is a full match, or null if not.
   1.920 +        function getFullyMatchedCommandOrNull(command) {
   1.921 +          if (keys.length < command.keys.length) {
   1.922 +            // Matches part of a multi-key command. Buffer and wait for next
   1.923 +            // stroke.
   1.924 +            inputState.keyBuffer.push(key);
   1.925 +            return null;
   1.926 +          } else {
   1.927 +            if (command.keys[keys.length - 1] == 'character') {
   1.928 +              inputState.selectedCharacter = selectedCharacter;
   1.929 +            }
   1.930 +            // Clear the buffer since a full match was found.
   1.931 +            inputState.keyBuffer = [];
   1.932 +            return command;
   1.933 +          }
   1.934 +        }
   1.935 +
   1.936 +        if (!matchedCommands.length) {
   1.937 +          // Clear the buffer since there were no matches.
   1.938 +          inputState.keyBuffer = [];
   1.939 +          return null;
   1.940 +        } else if (matchedCommands.length == 1) {
   1.941 +          return getFullyMatchedCommandOrNull(matchedCommands[0]);
   1.942 +        } else {
   1.943 +          // Find the best match in the list of matchedCommands.
   1.944 +          var context = vim.visualMode ? 'visual' : 'normal';
   1.945 +          var bestMatch; // Default to first in the list.
   1.946 +          for (var i = 0; i < matchedCommands.length; i++) {
   1.947 +            var current = matchedCommands[i];
   1.948 +            if (current.context == context) {
   1.949 +              bestMatch = current;
   1.950 +              break;
   1.951 +            } else if (!bestMatch && !current.context) {
   1.952 +              // Only set an imperfect match to best match if no best match is
   1.953 +              // set and the imperfect match is not restricted to another
   1.954 +              // context.
   1.955 +              bestMatch = current;
   1.956 +            }
   1.957 +          }
   1.958 +          return getFullyMatchedCommandOrNull(bestMatch);
   1.959 +        }
   1.960 +      },
   1.961 +      processCommand: function(cm, vim, command) {
   1.962 +        vim.inputState.repeatOverride = command.repeatOverride;
   1.963 +        switch (command.type) {
   1.964 +          case 'motion':
   1.965 +            this.processMotion(cm, vim, command);
   1.966 +            break;
   1.967 +          case 'operator':
   1.968 +            this.processOperator(cm, vim, command);
   1.969 +            break;
   1.970 +          case 'operatorMotion':
   1.971 +            this.processOperatorMotion(cm, vim, command);
   1.972 +            break;
   1.973 +          case 'action':
   1.974 +            this.processAction(cm, vim, command);
   1.975 +            break;
   1.976 +          case 'search':
   1.977 +            this.processSearch(cm, vim, command);
   1.978 +            break;
   1.979 +          case 'ex':
   1.980 +          case 'keyToEx':
   1.981 +            this.processEx(cm, vim, command);
   1.982 +            break;
   1.983 +          default:
   1.984 +            break;
   1.985 +        }
   1.986 +      },
   1.987 +      processMotion: function(cm, vim, command) {
   1.988 +        vim.inputState.motion = command.motion;
   1.989 +        vim.inputState.motionArgs = copyArgs(command.motionArgs);
   1.990 +        this.evalInput(cm, vim);
   1.991 +      },
   1.992 +      processOperator: function(cm, vim, command) {
   1.993 +        var inputState = vim.inputState;
   1.994 +        if (inputState.operator) {
   1.995 +          if (inputState.operator == command.operator) {
   1.996 +            // Typing an operator twice like 'dd' makes the operator operate
   1.997 +            // linewise
   1.998 +            inputState.motion = 'expandToLine';
   1.999 +            inputState.motionArgs = { linewise: true };
  1.1000 +            this.evalInput(cm, vim);
  1.1001 +            return;
  1.1002 +          } else {
  1.1003 +            // 2 different operators in a row doesn't make sense.
  1.1004 +            vim.inputState = new InputState();
  1.1005 +          }
  1.1006 +        }
  1.1007 +        inputState.operator = command.operator;
  1.1008 +        inputState.operatorArgs = copyArgs(command.operatorArgs);
  1.1009 +        if (vim.visualMode) {
  1.1010 +          // Operating on a selection in visual mode. We don't need a motion.
  1.1011 +          this.evalInput(cm, vim);
  1.1012 +        }
  1.1013 +      },
  1.1014 +      processOperatorMotion: function(cm, vim, command) {
  1.1015 +        var visualMode = vim.visualMode;
  1.1016 +        var operatorMotionArgs = copyArgs(command.operatorMotionArgs);
  1.1017 +        if (operatorMotionArgs) {
  1.1018 +          // Operator motions may have special behavior in visual mode.
  1.1019 +          if (visualMode && operatorMotionArgs.visualLine) {
  1.1020 +            vim.visualLine = true;
  1.1021 +          }
  1.1022 +        }
  1.1023 +        this.processOperator(cm, vim, command);
  1.1024 +        if (!visualMode) {
  1.1025 +          this.processMotion(cm, vim, command);
  1.1026 +        }
  1.1027 +      },
  1.1028 +      processAction: function(cm, vim, command) {
  1.1029 +        var inputState = vim.inputState;
  1.1030 +        var repeat = inputState.getRepeat();
  1.1031 +        var repeatIsExplicit = !!repeat;
  1.1032 +        var actionArgs = copyArgs(command.actionArgs) || {};
  1.1033 +        if (inputState.selectedCharacter) {
  1.1034 +          actionArgs.selectedCharacter = inputState.selectedCharacter;
  1.1035 +        }
  1.1036 +        // Actions may or may not have motions and operators. Do these first.
  1.1037 +        if (command.operator) {
  1.1038 +          this.processOperator(cm, vim, command);
  1.1039 +        }
  1.1040 +        if (command.motion) {
  1.1041 +          this.processMotion(cm, vim, command);
  1.1042 +        }
  1.1043 +        if (command.motion || command.operator) {
  1.1044 +          this.evalInput(cm, vim);
  1.1045 +        }
  1.1046 +        actionArgs.repeat = repeat || 1;
  1.1047 +        actionArgs.repeatIsExplicit = repeatIsExplicit;
  1.1048 +        actionArgs.registerName = inputState.registerName;
  1.1049 +        vim.inputState = new InputState();
  1.1050 +        vim.lastMotion = null;
  1.1051 +        if (command.isEdit) {
  1.1052 +          this.recordLastEdit(vim, inputState, command);
  1.1053 +        }
  1.1054 +        actions[command.action](cm, actionArgs, vim);
  1.1055 +      },
  1.1056 +      processSearch: function(cm, vim, command) {
  1.1057 +        if (!cm.getSearchCursor) {
  1.1058 +          // Search depends on SearchCursor.
  1.1059 +          return;
  1.1060 +        }
  1.1061 +        var forward = command.searchArgs.forward;
  1.1062 +        getSearchState(cm).setReversed(!forward);
  1.1063 +        var promptPrefix = (forward) ? '/' : '?';
  1.1064 +        var originalQuery = getSearchState(cm).getQuery();
  1.1065 +        var originalScrollPos = cm.getScrollInfo();
  1.1066 +        function handleQuery(query, ignoreCase, smartCase) {
  1.1067 +          try {
  1.1068 +            updateSearchQuery(cm, query, ignoreCase, smartCase);
  1.1069 +          } catch (e) {
  1.1070 +            showConfirm(cm, 'Invalid regex: ' + query);
  1.1071 +            return;
  1.1072 +          }
  1.1073 +          commandDispatcher.processMotion(cm, vim, {
  1.1074 +            type: 'motion',
  1.1075 +            motion: 'findNext',
  1.1076 +            motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }
  1.1077 +          });
  1.1078 +        }
  1.1079 +        function onPromptClose(query) {
  1.1080 +          cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
  1.1081 +          handleQuery(query, true /** ignoreCase */, true /** smartCase */);
  1.1082 +        }
  1.1083 +        function onPromptKeyUp(_e, query) {
  1.1084 +          var parsedQuery;
  1.1085 +          try {
  1.1086 +            parsedQuery = updateSearchQuery(cm, query,
  1.1087 +                true /** ignoreCase */, true /** smartCase */);
  1.1088 +          } catch (e) {
  1.1089 +            // Swallow bad regexes for incremental search.
  1.1090 +          }
  1.1091 +          if (parsedQuery) {
  1.1092 +            cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);
  1.1093 +          } else {
  1.1094 +            clearSearchHighlight(cm);
  1.1095 +            cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
  1.1096 +          }
  1.1097 +        }
  1.1098 +        function onPromptKeyDown(e, _query, close) {
  1.1099 +          var keyName = CodeMirror.keyName(e);
  1.1100 +          if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
  1.1101 +            updateSearchQuery(cm, originalQuery);
  1.1102 +            clearSearchHighlight(cm);
  1.1103 +            cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
  1.1104 +
  1.1105 +            CodeMirror.e_stop(e);
  1.1106 +            close();
  1.1107 +            cm.focus();
  1.1108 +          }
  1.1109 +        }
  1.1110 +        switch (command.searchArgs.querySrc) {
  1.1111 +          case 'prompt':
  1.1112 +            showPrompt(cm, {
  1.1113 +                onClose: onPromptClose,
  1.1114 +                prefix: promptPrefix,
  1.1115 +                desc: searchPromptDesc,
  1.1116 +                onKeyUp: onPromptKeyUp,
  1.1117 +                onKeyDown: onPromptKeyDown
  1.1118 +            });
  1.1119 +            break;
  1.1120 +          case 'wordUnderCursor':
  1.1121 +            var word = expandWordUnderCursor(cm, false /** inclusive */,
  1.1122 +                true /** forward */, false /** bigWord */,
  1.1123 +                true /** noSymbol */);
  1.1124 +            var isKeyword = true;
  1.1125 +            if (!word) {
  1.1126 +              word = expandWordUnderCursor(cm, false /** inclusive */,
  1.1127 +                  true /** forward */, false /** bigWord */,
  1.1128 +                  false /** noSymbol */);
  1.1129 +              isKeyword = false;
  1.1130 +            }
  1.1131 +            if (!word) {
  1.1132 +              return;
  1.1133 +            }
  1.1134 +            var query = cm.getLine(word.start.line).substring(word.start.ch,
  1.1135 +                word.end.ch);
  1.1136 +            if (isKeyword) {
  1.1137 +              query = '\\b' + query + '\\b';
  1.1138 +            } else {
  1.1139 +              query = escapeRegex(query);
  1.1140 +            }
  1.1141 +
  1.1142 +            // cachedCursor is used to save the old position of the cursor
  1.1143 +            // when * or # causes vim to seek for the nearest word and shift
  1.1144 +            // the cursor before entering the motion.
  1.1145 +            vimGlobalState.jumpList.cachedCursor = cm.getCursor();
  1.1146 +            cm.setCursor(word.start);
  1.1147 +
  1.1148 +            handleQuery(query, true /** ignoreCase */, false /** smartCase */);
  1.1149 +            break;
  1.1150 +        }
  1.1151 +      },
  1.1152 +      processEx: function(cm, vim, command) {
  1.1153 +        function onPromptClose(input) {
  1.1154 +          // Give the prompt some time to close so that if processCommand shows
  1.1155 +          // an error, the elements don't overlap.
  1.1156 +          exCommandDispatcher.processCommand(cm, input);
  1.1157 +        }
  1.1158 +        function onPromptKeyDown(e, _input, close) {
  1.1159 +          var keyName = CodeMirror.keyName(e);
  1.1160 +          if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
  1.1161 +            CodeMirror.e_stop(e);
  1.1162 +            close();
  1.1163 +            cm.focus();
  1.1164 +          }
  1.1165 +        }
  1.1166 +        if (command.type == 'keyToEx') {
  1.1167 +          // Handle user defined Ex to Ex mappings
  1.1168 +          exCommandDispatcher.processCommand(cm, command.exArgs.input);
  1.1169 +        } else {
  1.1170 +          if (vim.visualMode) {
  1.1171 +            showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
  1.1172 +                onKeyDown: onPromptKeyDown});
  1.1173 +          } else {
  1.1174 +            showPrompt(cm, { onClose: onPromptClose, prefix: ':',
  1.1175 +                onKeyDown: onPromptKeyDown});
  1.1176 +          }
  1.1177 +        }
  1.1178 +      },
  1.1179 +      evalInput: function(cm, vim) {
  1.1180 +        // If the motion comand is set, execute both the operator and motion.
  1.1181 +        // Otherwise return.
  1.1182 +        var inputState = vim.inputState;
  1.1183 +        var motion = inputState.motion;
  1.1184 +        var motionArgs = inputState.motionArgs || {};
  1.1185 +        var operator = inputState.operator;
  1.1186 +        var operatorArgs = inputState.operatorArgs || {};
  1.1187 +        var registerName = inputState.registerName;
  1.1188 +        var selectionEnd = copyCursor(cm.getCursor('head'));
  1.1189 +        var selectionStart = copyCursor(cm.getCursor('anchor'));
  1.1190 +        // The difference between cur and selection cursors are that cur is
  1.1191 +        // being operated on and ignores that there is a selection.
  1.1192 +        var curStart = copyCursor(selectionEnd);
  1.1193 +        var curOriginal = copyCursor(curStart);
  1.1194 +        var curEnd;
  1.1195 +        var repeat;
  1.1196 +        if (operator) {
  1.1197 +          this.recordLastEdit(vim, inputState);
  1.1198 +        }
  1.1199 +        if (inputState.repeatOverride !== undefined) {
  1.1200 +          // If repeatOverride is specified, that takes precedence over the
  1.1201 +          // input state's repeat. Used by Ex mode and can be user defined.
  1.1202 +          repeat = inputState.repeatOverride;
  1.1203 +        } else {
  1.1204 +          repeat = inputState.getRepeat();
  1.1205 +        }
  1.1206 +        if (repeat > 0 && motionArgs.explicitRepeat) {
  1.1207 +          motionArgs.repeatIsExplicit = true;
  1.1208 +        } else if (motionArgs.noRepeat ||
  1.1209 +            (!motionArgs.explicitRepeat && repeat === 0)) {
  1.1210 +          repeat = 1;
  1.1211 +          motionArgs.repeatIsExplicit = false;
  1.1212 +        }
  1.1213 +        if (inputState.selectedCharacter) {
  1.1214 +          // If there is a character input, stick it in all of the arg arrays.
  1.1215 +          motionArgs.selectedCharacter = operatorArgs.selectedCharacter =
  1.1216 +              inputState.selectedCharacter;
  1.1217 +        }
  1.1218 +        motionArgs.repeat = repeat;
  1.1219 +        vim.inputState = new InputState();
  1.1220 +        if (motion) {
  1.1221 +          var motionResult = motions[motion](cm, motionArgs, vim);
  1.1222 +          vim.lastMotion = motions[motion];
  1.1223 +          if (!motionResult) {
  1.1224 +            return;
  1.1225 +          }
  1.1226 +          if (motionArgs.toJumplist) {
  1.1227 +            var jumpList = vimGlobalState.jumpList;
  1.1228 +            // if the current motion is # or *, use cachedCursor
  1.1229 +            var cachedCursor = jumpList.cachedCursor;
  1.1230 +            if (cachedCursor) {
  1.1231 +              recordJumpPosition(cm, cachedCursor, motionResult);
  1.1232 +              delete jumpList.cachedCursor;
  1.1233 +            } else {
  1.1234 +              recordJumpPosition(cm, curOriginal, motionResult);
  1.1235 +            }
  1.1236 +          }
  1.1237 +          if (motionResult instanceof Array) {
  1.1238 +            curStart = motionResult[0];
  1.1239 +            curEnd = motionResult[1];
  1.1240 +          } else {
  1.1241 +            curEnd = motionResult;
  1.1242 +          }
  1.1243 +          // TODO: Handle null returns from motion commands better.
  1.1244 +          if (!curEnd) {
  1.1245 +            curEnd = Pos(curStart.line, curStart.ch);
  1.1246 +          }
  1.1247 +          if (vim.visualMode) {
  1.1248 +            // Check if the selection crossed over itself. Will need to shift
  1.1249 +            // the start point if that happened.
  1.1250 +            if (cursorIsBefore(selectionStart, selectionEnd) &&
  1.1251 +                (cursorEqual(selectionStart, curEnd) ||
  1.1252 +                    cursorIsBefore(curEnd, selectionStart))) {
  1.1253 +              // The end of the selection has moved from after the start to
  1.1254 +              // before the start. We will shift the start right by 1.
  1.1255 +              selectionStart.ch += 1;
  1.1256 +            } else if (cursorIsBefore(selectionEnd, selectionStart) &&
  1.1257 +                (cursorEqual(selectionStart, curEnd) ||
  1.1258 +                    cursorIsBefore(selectionStart, curEnd))) {
  1.1259 +              // The opposite happened. We will shift the start left by 1.
  1.1260 +              selectionStart.ch -= 1;
  1.1261 +            }
  1.1262 +            selectionEnd = curEnd;
  1.1263 +            selectionStart = (motionResult instanceof Array) ? curStart : selectionStart;
  1.1264 +            if (vim.visualLine) {
  1.1265 +              if (cursorIsBefore(selectionStart, selectionEnd)) {
  1.1266 +                selectionStart.ch = 0;
  1.1267 +
  1.1268 +                var lastLine = cm.lastLine();
  1.1269 +                if (selectionEnd.line > lastLine) {
  1.1270 +                  selectionEnd.line = lastLine;
  1.1271 +                }
  1.1272 +                selectionEnd.ch = lineLength(cm, selectionEnd.line);
  1.1273 +              } else {
  1.1274 +                selectionEnd.ch = 0;
  1.1275 +                selectionStart.ch = lineLength(cm, selectionStart.line);
  1.1276 +              }
  1.1277 +            }
  1.1278 +            cm.setSelection(selectionStart, selectionEnd);
  1.1279 +            updateMark(cm, vim, '<',
  1.1280 +                cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
  1.1281 +                    : selectionEnd);
  1.1282 +            updateMark(cm, vim, '>',
  1.1283 +                cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd
  1.1284 +                    : selectionStart);
  1.1285 +          } else if (!operator) {
  1.1286 +            curEnd = clipCursorToContent(cm, curEnd);
  1.1287 +            cm.setCursor(curEnd.line, curEnd.ch);
  1.1288 +          }
  1.1289 +        }
  1.1290 +
  1.1291 +        if (operator) {
  1.1292 +          var inverted = false;
  1.1293 +          vim.lastMotion = null;
  1.1294 +          operatorArgs.repeat = repeat; // Indent in visual mode needs this.
  1.1295 +          if (vim.visualMode) {
  1.1296 +            curStart = selectionStart;
  1.1297 +            curEnd = selectionEnd;
  1.1298 +            motionArgs.inclusive = true;
  1.1299 +          }
  1.1300 +          // Swap start and end if motion was backward.
  1.1301 +          if (cursorIsBefore(curEnd, curStart)) {
  1.1302 +            var tmp = curStart;
  1.1303 +            curStart = curEnd;
  1.1304 +            curEnd = tmp;
  1.1305 +            inverted = true;
  1.1306 +          }
  1.1307 +          if (motionArgs.inclusive && !(vim.visualMode && inverted)) {
  1.1308 +            // Move the selection end one to the right to include the last
  1.1309 +            // character.
  1.1310 +            curEnd.ch++;
  1.1311 +          }
  1.1312 +          var linewise = motionArgs.linewise ||
  1.1313 +              (vim.visualMode && vim.visualLine);
  1.1314 +          if (linewise) {
  1.1315 +            // Expand selection to entire line.
  1.1316 +            expandSelectionToLine(cm, curStart, curEnd);
  1.1317 +          } else if (motionArgs.forward) {
  1.1318 +            // Clip to trailing newlines only if the motion goes forward.
  1.1319 +            clipToLine(cm, curStart, curEnd);
  1.1320 +          }
  1.1321 +          operatorArgs.registerName = registerName;
  1.1322 +          // Keep track of linewise as it affects how paste and change behave.
  1.1323 +          operatorArgs.linewise = linewise;
  1.1324 +          operators[operator](cm, operatorArgs, vim, curStart,
  1.1325 +              curEnd, curOriginal);
  1.1326 +          if (vim.visualMode) {
  1.1327 +            exitVisualMode(cm);
  1.1328 +          }
  1.1329 +        }
  1.1330 +      },
  1.1331 +      recordLastEdit: function(vim, inputState, actionCommand) {
  1.1332 +        var macroModeState = vimGlobalState.macroModeState;
  1.1333 +        if (macroModeState.isPlaying) { return; }
  1.1334 +        vim.lastEditInputState = inputState;
  1.1335 +        vim.lastEditActionCommand = actionCommand;
  1.1336 +        macroModeState.lastInsertModeChanges.changes = [];
  1.1337 +        macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
  1.1338 +      }
  1.1339 +    };
  1.1340 +
  1.1341 +    /**
  1.1342 +     * typedef {Object{line:number,ch:number}} Cursor An object containing the
  1.1343 +     *     position of the cursor.
  1.1344 +     */
  1.1345 +    // All of the functions below return Cursor objects.
  1.1346 +    var motions = {
  1.1347 +      moveToTopLine: function(cm, motionArgs) {
  1.1348 +        var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
  1.1349 +        return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
  1.1350 +      },
  1.1351 +      moveToMiddleLine: function(cm) {
  1.1352 +        var range = getUserVisibleLines(cm);
  1.1353 +        var line = Math.floor((range.top + range.bottom) * 0.5);
  1.1354 +        return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
  1.1355 +      },
  1.1356 +      moveToBottomLine: function(cm, motionArgs) {
  1.1357 +        var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
  1.1358 +        return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
  1.1359 +      },
  1.1360 +      expandToLine: function(cm, motionArgs) {
  1.1361 +        // Expands forward to end of line, and then to next line if repeat is
  1.1362 +        // >1. Does not handle backward motion!
  1.1363 +        var cur = cm.getCursor();
  1.1364 +        return Pos(cur.line + motionArgs.repeat - 1, Infinity);
  1.1365 +      },
  1.1366 +      findNext: function(cm, motionArgs) {
  1.1367 +        var state = getSearchState(cm);
  1.1368 +        var query = state.getQuery();
  1.1369 +        if (!query) {
  1.1370 +          return;
  1.1371 +        }
  1.1372 +        var prev = !motionArgs.forward;
  1.1373 +        // If search is initiated with ? instead of /, negate direction.
  1.1374 +        prev = (state.isReversed()) ? !prev : prev;
  1.1375 +        highlightSearchMatches(cm, query);
  1.1376 +        return findNext(cm, prev/** prev */, query, motionArgs.repeat);
  1.1377 +      },
  1.1378 +      goToMark: function(_cm, motionArgs, vim) {
  1.1379 +        var mark = vim.marks[motionArgs.selectedCharacter];
  1.1380 +        if (mark) {
  1.1381 +          return mark.find();
  1.1382 +        }
  1.1383 +        return null;
  1.1384 +      },
  1.1385 +      moveToOtherHighlightedEnd: function(cm) {
  1.1386 +        var curEnd = copyCursor(cm.getCursor('head'));
  1.1387 +        var curStart = copyCursor(cm.getCursor('anchor'));
  1.1388 +        if (cursorIsBefore(curStart, curEnd)) {
  1.1389 +           curEnd.ch += 1;
  1.1390 +        } else if (cursorIsBefore(curEnd, curStart)) {
  1.1391 +           curStart.ch -= 1;
  1.1392 +        }
  1.1393 +        return ([curEnd,curStart]);
  1.1394 +      },
  1.1395 +      jumpToMark: function(cm, motionArgs, vim) {
  1.1396 +        var best = cm.getCursor();
  1.1397 +        for (var i = 0; i < motionArgs.repeat; i++) {
  1.1398 +          var cursor = best;
  1.1399 +          for (var key in vim.marks) {
  1.1400 +            if (!isLowerCase(key)) {
  1.1401 +              continue;
  1.1402 +            }
  1.1403 +            var mark = vim.marks[key].find();
  1.1404 +            var isWrongDirection = (motionArgs.forward) ?
  1.1405 +              cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);
  1.1406 +
  1.1407 +            if (isWrongDirection) {
  1.1408 +              continue;
  1.1409 +            }
  1.1410 +            if (motionArgs.linewise && (mark.line == cursor.line)) {
  1.1411 +              continue;
  1.1412 +            }
  1.1413 +
  1.1414 +            var equal = cursorEqual(cursor, best);
  1.1415 +            var between = (motionArgs.forward) ?
  1.1416 +              cusrorIsBetween(cursor, mark, best) :
  1.1417 +              cusrorIsBetween(best, mark, cursor);
  1.1418 +
  1.1419 +            if (equal || between) {
  1.1420 +              best = mark;
  1.1421 +            }
  1.1422 +          }
  1.1423 +        }
  1.1424 +
  1.1425 +        if (motionArgs.linewise) {
  1.1426 +          // Vim places the cursor on the first non-whitespace character of
  1.1427 +          // the line if there is one, else it places the cursor at the end
  1.1428 +          // of the line, regardless of whether a mark was found.
  1.1429 +          best = Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line)));
  1.1430 +        }
  1.1431 +        return best;
  1.1432 +      },
  1.1433 +      moveByCharacters: function(cm, motionArgs) {
  1.1434 +        var cur = cm.getCursor();
  1.1435 +        var repeat = motionArgs.repeat;
  1.1436 +        var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
  1.1437 +        return Pos(cur.line, ch);
  1.1438 +      },
  1.1439 +      moveByLines: function(cm, motionArgs, vim) {
  1.1440 +        var cur = cm.getCursor();
  1.1441 +        var endCh = cur.ch;
  1.1442 +        // Depending what our last motion was, we may want to do different
  1.1443 +        // things. If our last motion was moving vertically, we want to
  1.1444 +        // preserve the HPos from our last horizontal move.  If our last motion
  1.1445 +        // was going to the end of a line, moving vertically we should go to
  1.1446 +        // the end of the line, etc.
  1.1447 +        switch (vim.lastMotion) {
  1.1448 +          case this.moveByLines:
  1.1449 +          case this.moveByDisplayLines:
  1.1450 +          case this.moveByScroll:
  1.1451 +          case this.moveToColumn:
  1.1452 +          case this.moveToEol:
  1.1453 +            endCh = vim.lastHPos;
  1.1454 +            break;
  1.1455 +          default:
  1.1456 +            vim.lastHPos = endCh;
  1.1457 +        }
  1.1458 +        var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
  1.1459 +        var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
  1.1460 +        var first = cm.firstLine();
  1.1461 +        var last = cm.lastLine();
  1.1462 +        // Vim cancels linewise motions that start on an edge and move beyond
  1.1463 +        // that edge. It does not cancel motions that do not start on an edge.
  1.1464 +        if ((line < first && cur.line == first) ||
  1.1465 +            (line > last && cur.line == last)) {
  1.1466 +          return;
  1.1467 +        }
  1.1468 +        if (motionArgs.toFirstChar){
  1.1469 +          endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
  1.1470 +          vim.lastHPos = endCh;
  1.1471 +        }
  1.1472 +        vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left;
  1.1473 +        return Pos(line, endCh);
  1.1474 +      },
  1.1475 +      moveByDisplayLines: function(cm, motionArgs, vim) {
  1.1476 +        var cur = cm.getCursor();
  1.1477 +        switch (vim.lastMotion) {
  1.1478 +          case this.moveByDisplayLines:
  1.1479 +          case this.moveByScroll:
  1.1480 +          case this.moveByLines:
  1.1481 +          case this.moveToColumn:
  1.1482 +          case this.moveToEol:
  1.1483 +            break;
  1.1484 +          default:
  1.1485 +            vim.lastHSPos = cm.charCoords(cur,'div').left;
  1.1486 +        }
  1.1487 +        var repeat = motionArgs.repeat;
  1.1488 +        var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);
  1.1489 +        if (res.hitSide) {
  1.1490 +          if (motionArgs.forward) {
  1.1491 +            var lastCharCoords = cm.charCoords(res, 'div');
  1.1492 +            var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };
  1.1493 +            var res = cm.coordsChar(goalCoords, 'div');
  1.1494 +          } else {
  1.1495 +            var resCoords = cm.charCoords(Pos(cm.firstLine(), 0), 'div');
  1.1496 +            resCoords.left = vim.lastHSPos;
  1.1497 +            res = cm.coordsChar(resCoords, 'div');
  1.1498 +          }
  1.1499 +        }
  1.1500 +        vim.lastHPos = res.ch;
  1.1501 +        return res;
  1.1502 +      },
  1.1503 +      moveByPage: function(cm, motionArgs) {
  1.1504 +        // CodeMirror only exposes functions that move the cursor page down, so
  1.1505 +        // doing this bad hack to move the cursor and move it back. evalInput
  1.1506 +        // will move the cursor to where it should be in the end.
  1.1507 +        var curStart = cm.getCursor();
  1.1508 +        var repeat = motionArgs.repeat;
  1.1509 +        cm.moveV((motionArgs.forward ? repeat : -repeat), 'page');
  1.1510 +        var curEnd = cm.getCursor();
  1.1511 +        cm.setCursor(curStart);
  1.1512 +        return curEnd;
  1.1513 +      },
  1.1514 +      moveByParagraph: function(cm, motionArgs) {
  1.1515 +        var line = cm.getCursor().line;
  1.1516 +        var repeat = motionArgs.repeat;
  1.1517 +        var inc = motionArgs.forward ? 1 : -1;
  1.1518 +        for (var i = 0; i < repeat; i++) {
  1.1519 +          if ((!motionArgs.forward && line === cm.firstLine() ) ||
  1.1520 +              (motionArgs.forward && line == cm.lastLine())) {
  1.1521 +            break;
  1.1522 +          }
  1.1523 +          line += inc;
  1.1524 +          while (line !== cm.firstLine() && line != cm.lastLine() && cm.getLine(line)) {
  1.1525 +            line += inc;
  1.1526 +          }
  1.1527 +        }
  1.1528 +        return Pos(line, 0);
  1.1529 +      },
  1.1530 +      moveByScroll: function(cm, motionArgs, vim) {
  1.1531 +        var scrollbox = cm.getScrollInfo();
  1.1532 +        var curEnd = null;
  1.1533 +        var repeat = motionArgs.repeat;
  1.1534 +        if (!repeat) {
  1.1535 +          repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
  1.1536 +        }
  1.1537 +        var orig = cm.charCoords(cm.getCursor(), 'local');
  1.1538 +        motionArgs.repeat = repeat;
  1.1539 +        var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim);
  1.1540 +        if (!curEnd) {
  1.1541 +          return null;
  1.1542 +        }
  1.1543 +        var dest = cm.charCoords(curEnd, 'local');
  1.1544 +        cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
  1.1545 +        return curEnd;
  1.1546 +      },
  1.1547 +      moveByWords: function(cm, motionArgs) {
  1.1548 +        return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward,
  1.1549 +            !!motionArgs.wordEnd, !!motionArgs.bigWord);
  1.1550 +      },
  1.1551 +      moveTillCharacter: function(cm, motionArgs) {
  1.1552 +        var repeat = motionArgs.repeat;
  1.1553 +        var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
  1.1554 +            motionArgs.selectedCharacter);
  1.1555 +        var increment = motionArgs.forward ? -1 : 1;
  1.1556 +        recordLastCharacterSearch(increment, motionArgs);
  1.1557 +        if (!curEnd) return null;
  1.1558 +        curEnd.ch += increment;
  1.1559 +        return curEnd;
  1.1560 +      },
  1.1561 +      moveToCharacter: function(cm, motionArgs) {
  1.1562 +        var repeat = motionArgs.repeat;
  1.1563 +        recordLastCharacterSearch(0, motionArgs);
  1.1564 +        return moveToCharacter(cm, repeat, motionArgs.forward,
  1.1565 +            motionArgs.selectedCharacter) || cm.getCursor();
  1.1566 +      },
  1.1567 +      moveToSymbol: function(cm, motionArgs) {
  1.1568 +        var repeat = motionArgs.repeat;
  1.1569 +        return findSymbol(cm, repeat, motionArgs.forward,
  1.1570 +            motionArgs.selectedCharacter) || cm.getCursor();
  1.1571 +      },
  1.1572 +      moveToColumn: function(cm, motionArgs, vim) {
  1.1573 +        var repeat = motionArgs.repeat;
  1.1574 +        // repeat is equivalent to which column we want to move to!
  1.1575 +        vim.lastHPos = repeat - 1;
  1.1576 +        vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left;
  1.1577 +        return moveToColumn(cm, repeat);
  1.1578 +      },
  1.1579 +      moveToEol: function(cm, motionArgs, vim) {
  1.1580 +        var cur = cm.getCursor();
  1.1581 +        vim.lastHPos = Infinity;
  1.1582 +        var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity);
  1.1583 +        var end=cm.clipPos(retval);
  1.1584 +        end.ch--;
  1.1585 +        vim.lastHSPos = cm.charCoords(end,'div').left;
  1.1586 +        return retval;
  1.1587 +      },
  1.1588 +      moveToFirstNonWhiteSpaceCharacter: function(cm) {
  1.1589 +        // Go to the start of the line where the text begins, or the end for
  1.1590 +        // whitespace-only lines
  1.1591 +        var cursor = cm.getCursor();
  1.1592 +        return Pos(cursor.line,
  1.1593 +                   findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));
  1.1594 +      },
  1.1595 +      moveToMatchedSymbol: function(cm) {
  1.1596 +        var cursor = cm.getCursor();
  1.1597 +        var line = cursor.line;
  1.1598 +        var ch = cursor.ch;
  1.1599 +        var lineText = cm.getLine(line);
  1.1600 +        var symbol;
  1.1601 +        var startContext = cm.getTokenAt(cursor).type;
  1.1602 +        var startCtxLevel = getContextLevel(startContext);
  1.1603 +        do {
  1.1604 +          symbol = lineText.charAt(ch++);
  1.1605 +          if (symbol && isMatchableSymbol(symbol)) {
  1.1606 +            var endContext = cm.getTokenAt(Pos(line, ch)).type;
  1.1607 +            var endCtxLevel = getContextLevel(endContext);
  1.1608 +            if (startCtxLevel >= endCtxLevel) {
  1.1609 +              break;
  1.1610 +            }
  1.1611 +          }
  1.1612 +        } while (symbol);
  1.1613 +        if (symbol) {
  1.1614 +          return findMatchedSymbol(cm, Pos(line, ch-1), symbol);
  1.1615 +        } else {
  1.1616 +          return cursor;
  1.1617 +        }
  1.1618 +      },
  1.1619 +      moveToStartOfLine: function(cm) {
  1.1620 +        var cursor = cm.getCursor();
  1.1621 +        return Pos(cursor.line, 0);
  1.1622 +      },
  1.1623 +      moveToLineOrEdgeOfDocument: function(cm, motionArgs) {
  1.1624 +        var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
  1.1625 +        if (motionArgs.repeatIsExplicit) {
  1.1626 +          lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
  1.1627 +        }
  1.1628 +        return Pos(lineNum,
  1.1629 +                   findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)));
  1.1630 +      },
  1.1631 +      textObjectManipulation: function(cm, motionArgs) {
  1.1632 +        // TODO: lots of possible exceptions that can be thrown here. Try da(
  1.1633 +        //     outside of a () block.
  1.1634 +
  1.1635 +        // TODO: adding <> >< to this map doesn't work, presumably because
  1.1636 +        // they're operators
  1.1637 +        var mirroredPairs = {'(': ')', ')': '(',
  1.1638 +                             '{': '}', '}': '{',
  1.1639 +                             '[': ']', ']': '['};
  1.1640 +        var selfPaired = {'\'': true, '"': true};
  1.1641 +
  1.1642 +        var character = motionArgs.selectedCharacter;
  1.1643 +
  1.1644 +        // Inclusive is the difference between a and i
  1.1645 +        // TODO: Instead of using the additional text object map to perform text
  1.1646 +        //     object operations, merge the map into the defaultKeyMap and use
  1.1647 +        //     motionArgs to define behavior. Define separate entries for 'aw',
  1.1648 +        //     'iw', 'a[', 'i[', etc.
  1.1649 +        var inclusive = !motionArgs.textObjectInner;
  1.1650 +
  1.1651 +        var tmp;
  1.1652 +        if (mirroredPairs[character]) {
  1.1653 +          tmp = selectCompanionObject(cm, mirroredPairs[character], inclusive);
  1.1654 +        } else if (selfPaired[character]) {
  1.1655 +          tmp = findBeginningAndEnd(cm, character, inclusive);
  1.1656 +        } else if (character === 'W') {
  1.1657 +          tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
  1.1658 +                                                     true /** bigWord */);
  1.1659 +        } else if (character === 'w') {
  1.1660 +          tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
  1.1661 +                                                     false /** bigWord */);
  1.1662 +        } else {
  1.1663 +          // No text object defined for this, don't move.
  1.1664 +          return null;
  1.1665 +        }
  1.1666 +
  1.1667 +        return [tmp.start, tmp.end];
  1.1668 +      },
  1.1669 +
  1.1670 +      repeatLastCharacterSearch: function(cm, motionArgs) {
  1.1671 +        var lastSearch = vimGlobalState.lastChararacterSearch;
  1.1672 +        var repeat = motionArgs.repeat;
  1.1673 +        var forward = motionArgs.forward === lastSearch.forward;
  1.1674 +        var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
  1.1675 +        cm.moveH(-increment, 'char');
  1.1676 +        motionArgs.inclusive = forward ? true : false;
  1.1677 +        var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
  1.1678 +        if (!curEnd) {
  1.1679 +          cm.moveH(increment, 'char');
  1.1680 +          return cm.getCursor();
  1.1681 +        }
  1.1682 +        curEnd.ch += increment;
  1.1683 +        return curEnd;
  1.1684 +      }
  1.1685 +    };
  1.1686 +
  1.1687 +    var operators = {
  1.1688 +      change: function(cm, operatorArgs, _vim, curStart, curEnd) {
  1.1689 +        vimGlobalState.registerController.pushText(
  1.1690 +            operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd),
  1.1691 +            operatorArgs.linewise);
  1.1692 +        if (operatorArgs.linewise) {
  1.1693 +          // Push the next line back down, if there is a next line.
  1.1694 +          var replacement = curEnd.line > cm.lastLine() ? '' : '\n';
  1.1695 +          cm.replaceRange(replacement, curStart, curEnd);
  1.1696 +          cm.indentLine(curStart.line, 'smart');
  1.1697 +          // null ch so setCursor moves to end of line.
  1.1698 +          curStart.ch = null;
  1.1699 +        } else {
  1.1700 +          // Exclude trailing whitespace if the range is not all whitespace.
  1.1701 +          var text = cm.getRange(curStart, curEnd);
  1.1702 +          if (!isWhiteSpaceString(text)) {
  1.1703 +            var match = (/\s+$/).exec(text);
  1.1704 +            if (match) {
  1.1705 +              curEnd = offsetCursor(curEnd, 0, - match[0].length);
  1.1706 +            }
  1.1707 +          }
  1.1708 +          cm.replaceRange('', curStart, curEnd);
  1.1709 +        }
  1.1710 +        actions.enterInsertMode(cm, {}, cm.state.vim);
  1.1711 +        cm.setCursor(curStart);
  1.1712 +      },
  1.1713 +      // delete is a javascript keyword.
  1.1714 +      'delete': function(cm, operatorArgs, _vim, curStart, curEnd) {
  1.1715 +        // If the ending line is past the last line, inclusive, instead of
  1.1716 +        // including the trailing \n, include the \n before the starting line
  1.1717 +        if (operatorArgs.linewise &&
  1.1718 +            curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) {
  1.1719 +          curStart.line--;
  1.1720 +          curStart.ch = lineLength(cm, curStart.line);
  1.1721 +        }
  1.1722 +        vimGlobalState.registerController.pushText(
  1.1723 +            operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd),
  1.1724 +            operatorArgs.linewise);
  1.1725 +        cm.replaceRange('', curStart, curEnd);
  1.1726 +        if (operatorArgs.linewise) {
  1.1727 +          cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
  1.1728 +        } else {
  1.1729 +          cm.setCursor(curStart);
  1.1730 +        }
  1.1731 +      },
  1.1732 +      indent: function(cm, operatorArgs, vim, curStart, curEnd) {
  1.1733 +        var startLine = curStart.line;
  1.1734 +        var endLine = curEnd.line;
  1.1735 +        // In visual mode, n> shifts the selection right n times, instead of
  1.1736 +        // shifting n lines right once.
  1.1737 +        var repeat = (vim.visualMode) ? operatorArgs.repeat : 1;
  1.1738 +        if (operatorArgs.linewise) {
  1.1739 +          // The only way to delete a newline is to delete until the start of
  1.1740 +          // the next line, so in linewise mode evalInput will include the next
  1.1741 +          // line. We don't want this in indent, so we go back a line.
  1.1742 +          endLine--;
  1.1743 +        }
  1.1744 +        for (var i = startLine; i <= endLine; i++) {
  1.1745 +          for (var j = 0; j < repeat; j++) {
  1.1746 +            cm.indentLine(i, operatorArgs.indentRight);
  1.1747 +          }
  1.1748 +        }
  1.1749 +        cm.setCursor(curStart);
  1.1750 +        cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
  1.1751 +      },
  1.1752 +      swapcase: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
  1.1753 +        var toSwap = cm.getRange(curStart, curEnd);
  1.1754 +        var swapped = '';
  1.1755 +        for (var i = 0; i < toSwap.length; i++) {
  1.1756 +          var character = toSwap.charAt(i);
  1.1757 +          swapped += isUpperCase(character) ? character.toLowerCase() :
  1.1758 +              character.toUpperCase();
  1.1759 +        }
  1.1760 +        cm.replaceRange(swapped, curStart, curEnd);
  1.1761 +        if (!operatorArgs.shouldMoveCursor) {
  1.1762 +          cm.setCursor(curOriginal);
  1.1763 +        }
  1.1764 +      },
  1.1765 +      yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
  1.1766 +        vimGlobalState.registerController.pushText(
  1.1767 +            operatorArgs.registerName, 'yank',
  1.1768 +            cm.getRange(curStart, curEnd), operatorArgs.linewise);
  1.1769 +        cm.setCursor(curOriginal);
  1.1770 +      }
  1.1771 +    };
  1.1772 +
  1.1773 +    var actions = {
  1.1774 +      jumpListWalk: function(cm, actionArgs, vim) {
  1.1775 +        if (vim.visualMode) {
  1.1776 +          return;
  1.1777 +        }
  1.1778 +        var repeat = actionArgs.repeat;
  1.1779 +        var forward = actionArgs.forward;
  1.1780 +        var jumpList = vimGlobalState.jumpList;
  1.1781 +
  1.1782 +        var mark = jumpList.move(cm, forward ? repeat : -repeat);
  1.1783 +        var markPos = mark ? mark.find() : undefined;
  1.1784 +        markPos = markPos ? markPos : cm.getCursor();
  1.1785 +        cm.setCursor(markPos);
  1.1786 +      },
  1.1787 +      scroll: function(cm, actionArgs, vim) {
  1.1788 +        if (vim.visualMode) {
  1.1789 +          return;
  1.1790 +        }
  1.1791 +        var repeat = actionArgs.repeat || 1;
  1.1792 +        var lineHeight = cm.defaultTextHeight();
  1.1793 +        var top = cm.getScrollInfo().top;
  1.1794 +        var delta = lineHeight * repeat;
  1.1795 +        var newPos = actionArgs.forward ? top + delta : top - delta;
  1.1796 +        var cursor = copyCursor(cm.getCursor());
  1.1797 +        var cursorCoords = cm.charCoords(cursor, 'local');
  1.1798 +        if (actionArgs.forward) {
  1.1799 +          if (newPos > cursorCoords.top) {
  1.1800 +             cursor.line += (newPos - cursorCoords.top) / lineHeight;
  1.1801 +             cursor.line = Math.ceil(cursor.line);
  1.1802 +             cm.setCursor(cursor);
  1.1803 +             cursorCoords = cm.charCoords(cursor, 'local');
  1.1804 +             cm.scrollTo(null, cursorCoords.top);
  1.1805 +          } else {
  1.1806 +             // Cursor stays within bounds.  Just reposition the scroll window.
  1.1807 +             cm.scrollTo(null, newPos);
  1.1808 +          }
  1.1809 +        } else {
  1.1810 +          var newBottom = newPos + cm.getScrollInfo().clientHeight;
  1.1811 +          if (newBottom < cursorCoords.bottom) {
  1.1812 +             cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight;
  1.1813 +             cursor.line = Math.floor(cursor.line);
  1.1814 +             cm.setCursor(cursor);
  1.1815 +             cursorCoords = cm.charCoords(cursor, 'local');
  1.1816 +             cm.scrollTo(
  1.1817 +                 null, cursorCoords.bottom - cm.getScrollInfo().clientHeight);
  1.1818 +          } else {
  1.1819 +             // Cursor stays within bounds.  Just reposition the scroll window.
  1.1820 +             cm.scrollTo(null, newPos);
  1.1821 +          }
  1.1822 +        }
  1.1823 +      },
  1.1824 +      scrollToCursor: function(cm, actionArgs) {
  1.1825 +        var lineNum = cm.getCursor().line;
  1.1826 +        var charCoords = cm.charCoords(Pos(lineNum, 0), 'local');
  1.1827 +        var height = cm.getScrollInfo().clientHeight;
  1.1828 +        var y = charCoords.top;
  1.1829 +        var lineHeight = charCoords.bottom - y;
  1.1830 +        switch (actionArgs.position) {
  1.1831 +          case 'center': y = y - (height / 2) + lineHeight;
  1.1832 +            break;
  1.1833 +          case 'bottom': y = y - height + lineHeight*1.4;
  1.1834 +            break;
  1.1835 +          case 'top': y = y + lineHeight*0.4;
  1.1836 +            break;
  1.1837 +        }
  1.1838 +        cm.scrollTo(null, y);
  1.1839 +      },
  1.1840 +      replayMacro: function(cm, actionArgs, vim) {
  1.1841 +        var registerName = actionArgs.selectedCharacter;
  1.1842 +        var repeat = actionArgs.repeat;
  1.1843 +        var macroModeState = vimGlobalState.macroModeState;
  1.1844 +        if (registerName == '@') {
  1.1845 +          registerName = macroModeState.latestRegister;
  1.1846 +        }
  1.1847 +        while(repeat--){
  1.1848 +          executeMacroRegister(cm, vim, macroModeState, registerName);
  1.1849 +        }
  1.1850 +      },
  1.1851 +      enterMacroRecordMode: function(cm, actionArgs) {
  1.1852 +        var macroModeState = vimGlobalState.macroModeState;
  1.1853 +        var registerName = actionArgs.selectedCharacter;
  1.1854 +        macroModeState.enterMacroRecordMode(cm, registerName);
  1.1855 +      },
  1.1856 +      enterInsertMode: function(cm, actionArgs, vim) {
  1.1857 +        if (cm.getOption('readOnly')) { return; }
  1.1858 +        vim.insertMode = true;
  1.1859 +        vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
  1.1860 +        var insertAt = (actionArgs) ? actionArgs.insertAt : null;
  1.1861 +        if (insertAt == 'eol') {
  1.1862 +          var cursor = cm.getCursor();
  1.1863 +          cursor = Pos(cursor.line, lineLength(cm, cursor.line));
  1.1864 +          cm.setCursor(cursor);
  1.1865 +        } else if (insertAt == 'charAfter') {
  1.1866 +          cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
  1.1867 +        } else if (insertAt == 'firstNonBlank') {
  1.1868 +          cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
  1.1869 +        }
  1.1870 +        cm.setOption('keyMap', 'vim-insert');
  1.1871 +        cm.setOption('disableInput', false);
  1.1872 +        if (actionArgs && actionArgs.replace) {
  1.1873 +          // Handle Replace-mode as a special case of insert mode.
  1.1874 +          cm.toggleOverwrite(true);
  1.1875 +          cm.setOption('keyMap', 'vim-replace');
  1.1876 +          CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
  1.1877 +        } else {
  1.1878 +          cm.setOption('keyMap', 'vim-insert');
  1.1879 +          CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
  1.1880 +        }
  1.1881 +        if (!vimGlobalState.macroModeState.isPlaying) {
  1.1882 +          // Only record if not replaying.
  1.1883 +          cm.on('change', onChange);
  1.1884 +          cm.on('cursorActivity', onCursorActivity);
  1.1885 +          CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
  1.1886 +        }
  1.1887 +      },
  1.1888 +      toggleVisualMode: function(cm, actionArgs, vim) {
  1.1889 +        var repeat = actionArgs.repeat;
  1.1890 +        var curStart = cm.getCursor();
  1.1891 +        var curEnd;
  1.1892 +        // TODO: The repeat should actually select number of characters/lines
  1.1893 +        //     equal to the repeat times the size of the previous visual
  1.1894 +        //     operation.
  1.1895 +        if (!vim.visualMode) {
  1.1896 +          cm.on('mousedown', exitVisualMode);
  1.1897 +          vim.visualMode = true;
  1.1898 +          vim.visualLine = !!actionArgs.linewise;
  1.1899 +          if (vim.visualLine) {
  1.1900 +            curStart.ch = 0;
  1.1901 +            curEnd = clipCursorToContent(
  1.1902 +              cm, Pos(curStart.line + repeat - 1, lineLength(cm, curStart.line)),
  1.1903 +              true /** includeLineBreak */);
  1.1904 +          } else {
  1.1905 +            curEnd = clipCursorToContent(
  1.1906 +              cm, Pos(curStart.line, curStart.ch + repeat),
  1.1907 +              true /** includeLineBreak */);
  1.1908 +          }
  1.1909 +          // Make the initial selection.
  1.1910 +          if (!actionArgs.repeatIsExplicit && !vim.visualLine) {
  1.1911 +            // This is a strange case. Here the implicit repeat is 1. The
  1.1912 +            // following commands lets the cursor hover over the 1 character
  1.1913 +            // selection.
  1.1914 +            cm.setCursor(curEnd);
  1.1915 +            cm.setSelection(curEnd, curStart);
  1.1916 +          } else {
  1.1917 +            cm.setSelection(curStart, curEnd);
  1.1918 +          }
  1.1919 +          CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
  1.1920 +        } else {
  1.1921 +          curStart = cm.getCursor('anchor');
  1.1922 +          curEnd = cm.getCursor('head');
  1.1923 +          if (!vim.visualLine && actionArgs.linewise) {
  1.1924 +            // Shift-V pressed in characterwise visual mode. Switch to linewise
  1.1925 +            // visual mode instead of exiting visual mode.
  1.1926 +            vim.visualLine = true;
  1.1927 +            curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 :
  1.1928 +                lineLength(cm, curStart.line);
  1.1929 +            curEnd.ch = cursorIsBefore(curStart, curEnd) ?
  1.1930 +                lineLength(cm, curEnd.line) : 0;
  1.1931 +            cm.setSelection(curStart, curEnd);
  1.1932 +            CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"});
  1.1933 +          } else if (vim.visualLine && !actionArgs.linewise) {
  1.1934 +            // v pressed in linewise visual mode. Switch to characterwise visual
  1.1935 +            // mode instead of exiting visual mode.
  1.1936 +            vim.visualLine = false;
  1.1937 +            CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
  1.1938 +          } else {
  1.1939 +            exitVisualMode(cm);
  1.1940 +          }
  1.1941 +        }
  1.1942 +        updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart
  1.1943 +            : curEnd);
  1.1944 +        updateMark(cm, vim, '>', cursorIsBefore(curStart, curEnd) ? curEnd
  1.1945 +            : curStart);
  1.1946 +      },
  1.1947 +      reselectLastSelection: function(cm, _actionArgs, vim) {
  1.1948 +        if (vim.lastSelection) {
  1.1949 +          var lastSelection = vim.lastSelection;
  1.1950 +          cm.setSelection(lastSelection.curStart, lastSelection.curEnd);
  1.1951 +          if (lastSelection.visualLine) {
  1.1952 +            vim.visualMode = true;
  1.1953 +            vim.visualLine = true;
  1.1954 +          }
  1.1955 +          else {
  1.1956 +            vim.visualMode = true;
  1.1957 +            vim.visualLine = false;
  1.1958 +          }
  1.1959 +          CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
  1.1960 +        }
  1.1961 +      },
  1.1962 +      joinLines: function(cm, actionArgs, vim) {
  1.1963 +        var curStart, curEnd;
  1.1964 +        if (vim.visualMode) {
  1.1965 +          curStart = cm.getCursor('anchor');
  1.1966 +          curEnd = cm.getCursor('head');
  1.1967 +          curEnd.ch = lineLength(cm, curEnd.line) - 1;
  1.1968 +        } else {
  1.1969 +          // Repeat is the number of lines to join. Minimum 2 lines.
  1.1970 +          var repeat = Math.max(actionArgs.repeat, 2);
  1.1971 +          curStart = cm.getCursor();
  1.1972 +          curEnd = clipCursorToContent(cm, Pos(curStart.line + repeat - 1,
  1.1973 +                                               Infinity));
  1.1974 +        }
  1.1975 +        var finalCh = 0;
  1.1976 +        cm.operation(function() {
  1.1977 +          for (var i = curStart.line; i < curEnd.line; i++) {
  1.1978 +            finalCh = lineLength(cm, curStart.line);
  1.1979 +            var tmp = Pos(curStart.line + 1,
  1.1980 +                          lineLength(cm, curStart.line + 1));
  1.1981 +            var text = cm.getRange(curStart, tmp);
  1.1982 +            text = text.replace(/\n\s*/g, ' ');
  1.1983 +            cm.replaceRange(text, curStart, tmp);
  1.1984 +          }
  1.1985 +          var curFinalPos = Pos(curStart.line, finalCh);
  1.1986 +          cm.setCursor(curFinalPos);
  1.1987 +        });
  1.1988 +      },
  1.1989 +      newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
  1.1990 +        vim.insertMode = true;
  1.1991 +        var insertAt = copyCursor(cm.getCursor());
  1.1992 +        if (insertAt.line === cm.firstLine() && !actionArgs.after) {
  1.1993 +          // Special case for inserting newline before start of document.
  1.1994 +          cm.replaceRange('\n', Pos(cm.firstLine(), 0));
  1.1995 +          cm.setCursor(cm.firstLine(), 0);
  1.1996 +        } else {
  1.1997 +          insertAt.line = (actionArgs.after) ? insertAt.line :
  1.1998 +              insertAt.line - 1;
  1.1999 +          insertAt.ch = lineLength(cm, insertAt.line);
  1.2000 +          cm.setCursor(insertAt);
  1.2001 +          var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||
  1.2002 +              CodeMirror.commands.newlineAndIndent;
  1.2003 +          newlineFn(cm);
  1.2004 +        }
  1.2005 +        this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);
  1.2006 +      },
  1.2007 +      paste: function(cm, actionArgs) {
  1.2008 +        var cur = copyCursor(cm.getCursor());
  1.2009 +        var register = vimGlobalState.registerController.getRegister(
  1.2010 +            actionArgs.registerName);
  1.2011 +        var text = register.toString();
  1.2012 +        if (!text) {
  1.2013 +          return;
  1.2014 +        }
  1.2015 +        if (actionArgs.repeat > 1) {
  1.2016 +          var text = Array(actionArgs.repeat + 1).join(text);
  1.2017 +        }
  1.2018 +        var linewise = register.linewise;
  1.2019 +        if (linewise) {
  1.2020 +          if (actionArgs.after) {
  1.2021 +            // Move the newline at the end to the start instead, and paste just
  1.2022 +            // before the newline character of the line we are on right now.
  1.2023 +            text = '\n' + text.slice(0, text.length - 1);
  1.2024 +            cur.ch = lineLength(cm, cur.line);
  1.2025 +          } else {
  1.2026 +            cur.ch = 0;
  1.2027 +          }
  1.2028 +        } else {
  1.2029 +          cur.ch += actionArgs.after ? 1 : 0;
  1.2030 +        }
  1.2031 +        cm.replaceRange(text, cur);
  1.2032 +        // Now fine tune the cursor to where we want it.
  1.2033 +        var curPosFinal;
  1.2034 +        var idx;
  1.2035 +        if (linewise && actionArgs.after) {
  1.2036 +          curPosFinal = Pos(
  1.2037 +            cur.line + 1,
  1.2038 +            findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)));
  1.2039 +        } else if (linewise && !actionArgs.after) {
  1.2040 +          curPosFinal = Pos(
  1.2041 +            cur.line,
  1.2042 +            findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
  1.2043 +        } else if (!linewise && actionArgs.after) {
  1.2044 +          idx = cm.indexFromPos(cur);
  1.2045 +          curPosFinal = cm.posFromIndex(idx + text.length - 1);
  1.2046 +        } else {
  1.2047 +          idx = cm.indexFromPos(cur);
  1.2048 +          curPosFinal = cm.posFromIndex(idx + text.length);
  1.2049 +        }
  1.2050 +        cm.setCursor(curPosFinal);
  1.2051 +      },
  1.2052 +      undo: function(cm, actionArgs) {
  1.2053 +        cm.operation(function() {
  1.2054 +          repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
  1.2055 +          cm.setCursor(cm.getCursor('anchor'));
  1.2056 +        });
  1.2057 +      },
  1.2058 +      redo: function(cm, actionArgs) {
  1.2059 +        repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
  1.2060 +      },
  1.2061 +      setRegister: function(_cm, actionArgs, vim) {
  1.2062 +        vim.inputState.registerName = actionArgs.selectedCharacter;
  1.2063 +      },
  1.2064 +      setMark: function(cm, actionArgs, vim) {
  1.2065 +        var markName = actionArgs.selectedCharacter;
  1.2066 +        updateMark(cm, vim, markName, cm.getCursor());
  1.2067 +      },
  1.2068 +      replace: function(cm, actionArgs, vim) {
  1.2069 +        var replaceWith = actionArgs.selectedCharacter;
  1.2070 +        var curStart = cm.getCursor();
  1.2071 +        var replaceTo;
  1.2072 +        var curEnd;
  1.2073 +        if (vim.visualMode){
  1.2074 +          curStart=cm.getCursor('start');
  1.2075 +          curEnd=cm.getCursor('end');
  1.2076 +          // workaround to catch the character under the cursor
  1.2077 +          //  existing workaround doesn't cover actions
  1.2078 +          curEnd=cm.clipPos(Pos(curEnd.line, curEnd.ch+1));
  1.2079 +        }else{
  1.2080 +          var line = cm.getLine(curStart.line);
  1.2081 +          replaceTo = curStart.ch + actionArgs.repeat;
  1.2082 +          if (replaceTo > line.length) {
  1.2083 +            replaceTo=line.length;
  1.2084 +          }
  1.2085 +          curEnd = Pos(curStart.line, replaceTo);
  1.2086 +        }
  1.2087 +        if (replaceWith=='\n'){
  1.2088 +          if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
  1.2089 +          // special case, where vim help says to replace by just one line-break
  1.2090 +          (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
  1.2091 +        }else {
  1.2092 +          var replaceWithStr=cm.getRange(curStart, curEnd);
  1.2093 +          //replace all characters in range by selected, but keep linebreaks
  1.2094 +          replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith);
  1.2095 +          cm.replaceRange(replaceWithStr, curStart, curEnd);
  1.2096 +          if (vim.visualMode){
  1.2097 +            cm.setCursor(curStart);
  1.2098 +            exitVisualMode(cm);
  1.2099 +          }else{
  1.2100 +            cm.setCursor(offsetCursor(curEnd, 0, -1));
  1.2101 +          }
  1.2102 +        }
  1.2103 +      },
  1.2104 +      incrementNumberToken: function(cm, actionArgs) {
  1.2105 +        var cur = cm.getCursor();
  1.2106 +        var lineStr = cm.getLine(cur.line);
  1.2107 +        var re = /-?\d+/g;
  1.2108 +        var match;
  1.2109 +        var start;
  1.2110 +        var end;
  1.2111 +        var numberStr;
  1.2112 +        var token;
  1.2113 +        while ((match = re.exec(lineStr)) !== null) {
  1.2114 +          token = match[0];
  1.2115 +          start = match.index;
  1.2116 +          end = start + token.length;
  1.2117 +          if (cur.ch < end)break;
  1.2118 +        }
  1.2119 +        if (!actionArgs.backtrack && (end <= cur.ch))return;
  1.2120 +        if (token) {
  1.2121 +          var increment = actionArgs.increase ? 1 : -1;
  1.2122 +          var number = parseInt(token) + (increment * actionArgs.repeat);
  1.2123 +          var from = Pos(cur.line, start);
  1.2124 +          var to = Pos(cur.line, end);
  1.2125 +          numberStr = number.toString();
  1.2126 +          cm.replaceRange(numberStr, from, to);
  1.2127 +        } else {
  1.2128 +          return;
  1.2129 +        }
  1.2130 +        cm.setCursor(Pos(cur.line, start + numberStr.length - 1));
  1.2131 +      },
  1.2132 +      repeatLastEdit: function(cm, actionArgs, vim) {
  1.2133 +        var lastEditInputState = vim.lastEditInputState;
  1.2134 +        if (!lastEditInputState) { return; }
  1.2135 +        var repeat = actionArgs.repeat;
  1.2136 +        if (repeat && actionArgs.repeatIsExplicit) {
  1.2137 +          vim.lastEditInputState.repeatOverride = repeat;
  1.2138 +        } else {
  1.2139 +          repeat = vim.lastEditInputState.repeatOverride || repeat;
  1.2140 +        }
  1.2141 +        repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
  1.2142 +      }
  1.2143 +    };
  1.2144 +
  1.2145 +    /*
  1.2146 +     * Below are miscellaneous utility functions used by vim.js
  1.2147 +     */
  1.2148 +
  1.2149 +    /**
  1.2150 +     * Clips cursor to ensure that line is within the buffer's range
  1.2151 +     * If includeLineBreak is true, then allow cur.ch == lineLength.
  1.2152 +     */
  1.2153 +    function clipCursorToContent(cm, cur, includeLineBreak) {
  1.2154 +      var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );
  1.2155 +      var maxCh = lineLength(cm, line) - 1;
  1.2156 +      maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
  1.2157 +      var ch = Math.min(Math.max(0, cur.ch), maxCh);
  1.2158 +      return Pos(line, ch);
  1.2159 +    }
  1.2160 +    function copyArgs(args) {
  1.2161 +      var ret = {};
  1.2162 +      for (var prop in args) {
  1.2163 +        if (args.hasOwnProperty(prop)) {
  1.2164 +          ret[prop] = args[prop];
  1.2165 +        }
  1.2166 +      }
  1.2167 +      return ret;
  1.2168 +    }
  1.2169 +    function offsetCursor(cur, offsetLine, offsetCh) {
  1.2170 +      return Pos(cur.line + offsetLine, cur.ch + offsetCh);
  1.2171 +    }
  1.2172 +    function matchKeysPartial(pressed, mapped) {
  1.2173 +      for (var i = 0; i < pressed.length; i++) {
  1.2174 +        // 'character' means any character. For mark, register commads, etc.
  1.2175 +        if (pressed[i] != mapped[i] && mapped[i] != 'character') {
  1.2176 +          return false;
  1.2177 +        }
  1.2178 +      }
  1.2179 +      return true;
  1.2180 +    }
  1.2181 +    function repeatFn(cm, fn, repeat) {
  1.2182 +      return function() {
  1.2183 +        for (var i = 0; i < repeat; i++) {
  1.2184 +          fn(cm);
  1.2185 +        }
  1.2186 +      };
  1.2187 +    }
  1.2188 +    function copyCursor(cur) {
  1.2189 +      return Pos(cur.line, cur.ch);
  1.2190 +    }
  1.2191 +    function cursorEqual(cur1, cur2) {
  1.2192 +      return cur1.ch == cur2.ch && cur1.line == cur2.line;
  1.2193 +    }
  1.2194 +    function cursorIsBefore(cur1, cur2) {
  1.2195 +      if (cur1.line < cur2.line) {
  1.2196 +        return true;
  1.2197 +      }
  1.2198 +      if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
  1.2199 +        return true;
  1.2200 +      }
  1.2201 +      return false;
  1.2202 +    }
  1.2203 +    function cusrorIsBetween(cur1, cur2, cur3) {
  1.2204 +      // returns true if cur2 is between cur1 and cur3.
  1.2205 +      var cur1before2 = cursorIsBefore(cur1, cur2);
  1.2206 +      var cur2before3 = cursorIsBefore(cur2, cur3);
  1.2207 +      return cur1before2 && cur2before3;
  1.2208 +    }
  1.2209 +    function lineLength(cm, lineNum) {
  1.2210 +      return cm.getLine(lineNum).length;
  1.2211 +    }
  1.2212 +    function reverse(s){
  1.2213 +      return s.split('').reverse().join('');
  1.2214 +    }
  1.2215 +    function trim(s) {
  1.2216 +      if (s.trim) {
  1.2217 +        return s.trim();
  1.2218 +      }
  1.2219 +      return s.replace(/^\s+|\s+$/g, '');
  1.2220 +    }
  1.2221 +    function escapeRegex(s) {
  1.2222 +      return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
  1.2223 +    }
  1.2224 +
  1.2225 +    function exitVisualMode(cm) {
  1.2226 +      cm.off('mousedown', exitVisualMode);
  1.2227 +      var vim = cm.state.vim;
  1.2228 +      // can't use selection state here because yank has already reset its cursor
  1.2229 +      vim.lastSelection = {'curStart': vim.marks['<'].find(),
  1.2230 +        'curEnd': vim.marks['>'].find(), 'visualMode': vim.visualMode,
  1.2231 +        'visualLine': vim.visualLine};
  1.2232 +      vim.visualMode = false;
  1.2233 +      vim.visualLine = false;
  1.2234 +      var selectionStart = cm.getCursor('anchor');
  1.2235 +      var selectionEnd = cm.getCursor('head');
  1.2236 +      if (!cursorEqual(selectionStart, selectionEnd)) {
  1.2237 +        // Clear the selection and set the cursor only if the selection has not
  1.2238 +        // already been cleared. Otherwise we risk moving the cursor somewhere
  1.2239 +        // it's not supposed to be.
  1.2240 +        cm.setCursor(clipCursorToContent(cm, selectionEnd));
  1.2241 +      }
  1.2242 +      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
  1.2243 +    }
  1.2244 +
  1.2245 +    // Remove any trailing newlines from the selection. For
  1.2246 +    // example, with the caret at the start of the last word on the line,
  1.2247 +    // 'dw' should word, but not the newline, while 'w' should advance the
  1.2248 +    // caret to the first character of the next line.
  1.2249 +    function clipToLine(cm, curStart, curEnd) {
  1.2250 +      var selection = cm.getRange(curStart, curEnd);
  1.2251 +      // Only clip if the selection ends with trailing newline + whitespace
  1.2252 +      if (/\n\s*$/.test(selection)) {
  1.2253 +        var lines = selection.split('\n');
  1.2254 +        // We know this is all whitepsace.
  1.2255 +        lines.pop();
  1.2256 +
  1.2257 +        // Cases:
  1.2258 +        // 1. Last word is an empty line - do not clip the trailing '\n'
  1.2259 +        // 2. Last word is not an empty line - clip the trailing '\n'
  1.2260 +        var line;
  1.2261 +        // Find the line containing the last word, and clip all whitespace up
  1.2262 +        // to it.
  1.2263 +        for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
  1.2264 +          curEnd.line--;
  1.2265 +          curEnd.ch = 0;
  1.2266 +        }
  1.2267 +        // If the last word is not an empty line, clip an additional newline
  1.2268 +        if (line) {
  1.2269 +          curEnd.line--;
  1.2270 +          curEnd.ch = lineLength(cm, curEnd.line);
  1.2271 +        } else {
  1.2272 +          curEnd.ch = 0;
  1.2273 +        }
  1.2274 +      }
  1.2275 +    }
  1.2276 +
  1.2277 +    // Expand the selection to line ends.
  1.2278 +    function expandSelectionToLine(_cm, curStart, curEnd) {
  1.2279 +      curStart.ch = 0;
  1.2280 +      curEnd.ch = 0;
  1.2281 +      curEnd.line++;
  1.2282 +    }
  1.2283 +
  1.2284 +    function findFirstNonWhiteSpaceCharacter(text) {
  1.2285 +      if (!text) {
  1.2286 +        return 0;
  1.2287 +      }
  1.2288 +      var firstNonWS = text.search(/\S/);
  1.2289 +      return firstNonWS == -1 ? text.length : firstNonWS;
  1.2290 +    }
  1.2291 +
  1.2292 +    function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
  1.2293 +      var cur = cm.getCursor();
  1.2294 +      var line = cm.getLine(cur.line);
  1.2295 +      var idx = cur.ch;
  1.2296 +
  1.2297 +      // Seek to first word or non-whitespace character, depending on if
  1.2298 +      // noSymbol is true.
  1.2299 +      var textAfterIdx = line.substring(idx);
  1.2300 +      var firstMatchedChar;
  1.2301 +      if (noSymbol) {
  1.2302 +        firstMatchedChar = textAfterIdx.search(/\w/);
  1.2303 +      } else {
  1.2304 +        firstMatchedChar = textAfterIdx.search(/\S/);
  1.2305 +      }
  1.2306 +      if (firstMatchedChar == -1) {
  1.2307 +        return null;
  1.2308 +      }
  1.2309 +      idx += firstMatchedChar;
  1.2310 +      textAfterIdx = line.substring(idx);
  1.2311 +      var textBeforeIdx = line.substring(0, idx);
  1.2312 +
  1.2313 +      var matchRegex;
  1.2314 +      // Greedy matchers for the "word" we are trying to expand.
  1.2315 +      if (bigWord) {
  1.2316 +        matchRegex = /^\S+/;
  1.2317 +      } else {
  1.2318 +        if ((/\w/).test(line.charAt(idx))) {
  1.2319 +          matchRegex = /^\w+/;
  1.2320 +        } else {
  1.2321 +          matchRegex = /^[^\w\s]+/;
  1.2322 +        }
  1.2323 +      }
  1.2324 +
  1.2325 +      var wordAfterRegex = matchRegex.exec(textAfterIdx);
  1.2326 +      var wordStart = idx;
  1.2327 +      var wordEnd = idx + wordAfterRegex[0].length;
  1.2328 +      // TODO: Find a better way to do this. It will be slow on very long lines.
  1.2329 +      var revTextBeforeIdx = reverse(textBeforeIdx);
  1.2330 +      var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx);
  1.2331 +      if (wordBeforeRegex) {
  1.2332 +        wordStart -= wordBeforeRegex[0].length;
  1.2333 +      }
  1.2334 +
  1.2335 +      if (inclusive) {
  1.2336 +        // If present, trim all whitespace after word.
  1.2337 +        // Otherwise, trim all whitespace before word.
  1.2338 +        var textAfterWordEnd = line.substring(wordEnd);
  1.2339 +        var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length;
  1.2340 +        if (whitespacesAfterWord > 0) {
  1.2341 +          wordEnd += whitespacesAfterWord;
  1.2342 +        } else {
  1.2343 +          var revTrim = revTextBeforeIdx.length - wordStart;
  1.2344 +          var textBeforeWordStart = revTextBeforeIdx.substring(revTrim);
  1.2345 +          var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length;
  1.2346 +          wordStart -= whitespacesBeforeWord;
  1.2347 +        }
  1.2348 +      }
  1.2349 +
  1.2350 +      return { start: Pos(cur.line, wordStart),
  1.2351 +               end: Pos(cur.line, wordEnd) };
  1.2352 +    }
  1.2353 +
  1.2354 +    function recordJumpPosition(cm, oldCur, newCur) {
  1.2355 +      if (!cursorEqual(oldCur, newCur)) {
  1.2356 +        vimGlobalState.jumpList.add(cm, oldCur, newCur);
  1.2357 +      }
  1.2358 +    }
  1.2359 +
  1.2360 +    function recordLastCharacterSearch(increment, args) {
  1.2361 +        vimGlobalState.lastChararacterSearch.increment = increment;
  1.2362 +        vimGlobalState.lastChararacterSearch.forward = args.forward;
  1.2363 +        vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter;
  1.2364 +    }
  1.2365 +
  1.2366 +    var symbolToMode = {
  1.2367 +        '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',
  1.2368 +        '[': 'section', ']': 'section',
  1.2369 +        '*': 'comment', '/': 'comment',
  1.2370 +        'm': 'method', 'M': 'method',
  1.2371 +        '#': 'preprocess'
  1.2372 +    };
  1.2373 +    var findSymbolModes = {
  1.2374 +      bracket: {
  1.2375 +        isComplete: function(state) {
  1.2376 +          if (state.nextCh === state.symb) {
  1.2377 +            state.depth++;
  1.2378 +            if (state.depth >= 1)return true;
  1.2379 +          } else if (state.nextCh === state.reverseSymb) {
  1.2380 +            state.depth--;
  1.2381 +          }
  1.2382 +          return false;
  1.2383 +        }
  1.2384 +      },
  1.2385 +      section: {
  1.2386 +        init: function(state) {
  1.2387 +          state.curMoveThrough = true;
  1.2388 +          state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';
  1.2389 +        },
  1.2390 +        isComplete: function(state) {
  1.2391 +          return state.index === 0 && state.nextCh === state.symb;
  1.2392 +        }
  1.2393 +      },
  1.2394 +      comment: {
  1.2395 +        isComplete: function(state) {
  1.2396 +          var found = state.lastCh === '*' && state.nextCh === '/';
  1.2397 +          state.lastCh = state.nextCh;
  1.2398 +          return found;
  1.2399 +        }
  1.2400 +      },
  1.2401 +      // TODO: The original Vim implementation only operates on level 1 and 2.
  1.2402 +      // The current implementation doesn't check for code block level and
  1.2403 +      // therefore it operates on any levels.
  1.2404 +      method: {
  1.2405 +        init: function(state) {
  1.2406 +          state.symb = (state.symb === 'm' ? '{' : '}');
  1.2407 +          state.reverseSymb = state.symb === '{' ? '}' : '{';
  1.2408 +        },
  1.2409 +        isComplete: function(state) {
  1.2410 +          if (state.nextCh === state.symb)return true;
  1.2411 +          return false;
  1.2412 +        }
  1.2413 +      },
  1.2414 +      preprocess: {
  1.2415 +        init: function(state) {
  1.2416 +          state.index = 0;
  1.2417 +        },
  1.2418 +        isComplete: function(state) {
  1.2419 +          if (state.nextCh === '#') {
  1.2420 +            var token = state.lineText.match(/#(\w+)/)[1];
  1.2421 +            if (token === 'endif') {
  1.2422 +              if (state.forward && state.depth === 0) {
  1.2423 +                return true;
  1.2424 +              }
  1.2425 +              state.depth++;
  1.2426 +            } else if (token === 'if') {
  1.2427 +              if (!state.forward && state.depth === 0) {
  1.2428 +                return true;
  1.2429 +              }
  1.2430 +              state.depth--;
  1.2431 +            }
  1.2432 +            if (token === 'else' && state.depth === 0)return true;
  1.2433 +          }
  1.2434 +          return false;
  1.2435 +        }
  1.2436 +      }
  1.2437 +    };
  1.2438 +    function findSymbol(cm, repeat, forward, symb) {
  1.2439 +      var cur = copyCursor(cm.getCursor());
  1.2440 +      var increment = forward ? 1 : -1;
  1.2441 +      var endLine = forward ? cm.lineCount() : -1;
  1.2442 +      var curCh = cur.ch;
  1.2443 +      var line = cur.line;
  1.2444 +      var lineText = cm.getLine(line);
  1.2445 +      var state = {
  1.2446 +        lineText: lineText,
  1.2447 +        nextCh: lineText.charAt(curCh),
  1.2448 +        lastCh: null,
  1.2449 +        index: curCh,
  1.2450 +        symb: symb,
  1.2451 +        reverseSymb: (forward ?  { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
  1.2452 +        forward: forward,
  1.2453 +        depth: 0,
  1.2454 +        curMoveThrough: false
  1.2455 +      };
  1.2456 +      var mode = symbolToMode[symb];
  1.2457 +      if (!mode)return cur;
  1.2458 +      var init = findSymbolModes[mode].init;
  1.2459 +      var isComplete = findSymbolModes[mode].isComplete;
  1.2460 +      if (init) { init(state); }
  1.2461 +      while (line !== endLine && repeat) {
  1.2462 +        state.index += increment;
  1.2463 +        state.nextCh = state.lineText.charAt(state.index);
  1.2464 +        if (!state.nextCh) {
  1.2465 +          line += increment;
  1.2466 +          state.lineText = cm.getLine(line) || '';
  1.2467 +          if (increment > 0) {
  1.2468 +            state.index = 0;
  1.2469 +          } else {
  1.2470 +            var lineLen = state.lineText.length;
  1.2471 +            state.index = (lineLen > 0) ? (lineLen-1) : 0;
  1.2472 +          }
  1.2473 +          state.nextCh = state.lineText.charAt(state.index);
  1.2474 +        }
  1.2475 +        if (isComplete(state)) {
  1.2476 +          cur.line = line;
  1.2477 +          cur.ch = state.index;
  1.2478 +          repeat--;
  1.2479 +        }
  1.2480 +      }
  1.2481 +      if (state.nextCh || state.curMoveThrough) {
  1.2482 +        return Pos(line, state.index);
  1.2483 +      }
  1.2484 +      return cur;
  1.2485 +    }
  1.2486 +
  1.2487 +    /*
  1.2488 +     * Returns the boundaries of the next word. If the cursor in the middle of
  1.2489 +     * the word, then returns the boundaries of the current word, starting at
  1.2490 +     * the cursor. If the cursor is at the start/end of a word, and we are going
  1.2491 +     * forward/backward, respectively, find the boundaries of the next word.
  1.2492 +     *
  1.2493 +     * @param {CodeMirror} cm CodeMirror object.
  1.2494 +     * @param {Cursor} cur The cursor position.
  1.2495 +     * @param {boolean} forward True to search forward. False to search
  1.2496 +     *     backward.
  1.2497 +     * @param {boolean} bigWord True if punctuation count as part of the word.
  1.2498 +     *     False if only [a-zA-Z0-9] characters count as part of the word.
  1.2499 +     * @param {boolean} emptyLineIsWord True if empty lines should be treated
  1.2500 +     *     as words.
  1.2501 +     * @return {Object{from:number, to:number, line: number}} The boundaries of
  1.2502 +     *     the word, or null if there are no more words.
  1.2503 +     */
  1.2504 +    function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {
  1.2505 +      var lineNum = cur.line;
  1.2506 +      var pos = cur.ch;
  1.2507 +      var line = cm.getLine(lineNum);
  1.2508 +      var dir = forward ? 1 : -1;
  1.2509 +      var regexps = bigWord ? bigWordRegexp : wordRegexp;
  1.2510 +
  1.2511 +      if (emptyLineIsWord && line == '') {
  1.2512 +        lineNum += dir;
  1.2513 +        line = cm.getLine(lineNum);
  1.2514 +        if (!isLine(cm, lineNum)) {
  1.2515 +          return null;
  1.2516 +        }
  1.2517 +        pos = (forward) ? 0 : line.length;
  1.2518 +      }
  1.2519 +
  1.2520 +      while (true) {
  1.2521 +        if (emptyLineIsWord && line == '') {
  1.2522 +          return { from: 0, to: 0, line: lineNum };
  1.2523 +        }
  1.2524 +        var stop = (dir > 0) ? line.length : -1;
  1.2525 +        var wordStart = stop, wordEnd = stop;
  1.2526 +        // Find bounds of next word.
  1.2527 +        while (pos != stop) {
  1.2528 +          var foundWord = false;
  1.2529 +          for (var i = 0; i < regexps.length && !foundWord; ++i) {
  1.2530 +            if (regexps[i].test(line.charAt(pos))) {
  1.2531 +              wordStart = pos;
  1.2532 +              // Advance to end of word.
  1.2533 +              while (pos != stop && regexps[i].test(line.charAt(pos))) {
  1.2534 +                pos += dir;
  1.2535 +              }
  1.2536 +              wordEnd = pos;
  1.2537 +              foundWord = wordStart != wordEnd;
  1.2538 +              if (wordStart == cur.ch && lineNum == cur.line &&
  1.2539 +                  wordEnd == wordStart + dir) {
  1.2540 +                // We started at the end of a word. Find the next one.
  1.2541 +                continue;
  1.2542 +              } else {
  1.2543 +                return {
  1.2544 +                  from: Math.min(wordStart, wordEnd + 1),
  1.2545 +                  to: Math.max(wordStart, wordEnd),
  1.2546 +                  line: lineNum };
  1.2547 +              }
  1.2548 +            }
  1.2549 +          }
  1.2550 +          if (!foundWord) {
  1.2551 +            pos += dir;
  1.2552 +          }
  1.2553 +        }
  1.2554 +        // Advance to next/prev line.
  1.2555 +        lineNum += dir;
  1.2556 +        if (!isLine(cm, lineNum)) {
  1.2557 +          return null;
  1.2558 +        }
  1.2559 +        line = cm.getLine(lineNum);
  1.2560 +        pos = (dir > 0) ? 0 : line.length;
  1.2561 +      }
  1.2562 +      // Should never get here.
  1.2563 +      throw new Error('The impossible happened.');
  1.2564 +    }
  1.2565 +
  1.2566 +    /**
  1.2567 +     * @param {CodeMirror} cm CodeMirror object.
  1.2568 +     * @param {int} repeat Number of words to move past.
  1.2569 +     * @param {boolean} forward True to search forward. False to search
  1.2570 +     *     backward.
  1.2571 +     * @param {boolean} wordEnd True to move to end of word. False to move to
  1.2572 +     *     beginning of word.
  1.2573 +     * @param {boolean} bigWord True if punctuation count as part of the word.
  1.2574 +     *     False if only alphabet characters count as part of the word.
  1.2575 +     * @return {Cursor} The position the cursor should move to.
  1.2576 +     */
  1.2577 +    function moveToWord(cm, repeat, forward, wordEnd, bigWord) {
  1.2578 +      var cur = cm.getCursor();
  1.2579 +      var curStart = copyCursor(cur);
  1.2580 +      var words = [];
  1.2581 +      if (forward && !wordEnd || !forward && wordEnd) {
  1.2582 +        repeat++;
  1.2583 +      }
  1.2584 +      // For 'e', empty lines are not considered words, go figure.
  1.2585 +      var emptyLineIsWord = !(forward && wordEnd);
  1.2586 +      for (var i = 0; i < repeat; i++) {
  1.2587 +        var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);
  1.2588 +        if (!word) {
  1.2589 +          var eodCh = lineLength(cm, cm.lastLine());
  1.2590 +          words.push(forward
  1.2591 +              ? {line: cm.lastLine(), from: eodCh, to: eodCh}
  1.2592 +              : {line: 0, from: 0, to: 0});
  1.2593 +          break;
  1.2594 +        }
  1.2595 +        words.push(word);
  1.2596 +        cur = Pos(word.line, forward ? (word.to - 1) : word.from);
  1.2597 +      }
  1.2598 +      var shortCircuit = words.length != repeat;
  1.2599 +      var firstWord = words[0];
  1.2600 +      var lastWord = words.pop();
  1.2601 +      if (forward && !wordEnd) {
  1.2602 +        // w
  1.2603 +        if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {
  1.2604 +          // We did not start in the middle of a word. Discard the extra word at the end.
  1.2605 +          lastWord = words.pop();
  1.2606 +        }
  1.2607 +        return Pos(lastWord.line, lastWord.from);
  1.2608 +      } else if (forward && wordEnd) {
  1.2609 +        return Pos(lastWord.line, lastWord.to - 1);
  1.2610 +      } else if (!forward && wordEnd) {
  1.2611 +        // ge
  1.2612 +        if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {
  1.2613 +          // We did not start in the middle of a word. Discard the extra word at the end.
  1.2614 +          lastWord = words.pop();
  1.2615 +        }
  1.2616 +        return Pos(lastWord.line, lastWord.to);
  1.2617 +      } else {
  1.2618 +        // b
  1.2619 +        return Pos(lastWord.line, lastWord.from);
  1.2620 +      }
  1.2621 +    }
  1.2622 +
  1.2623 +    function moveToCharacter(cm, repeat, forward, character) {
  1.2624 +      var cur = cm.getCursor();
  1.2625 +      var start = cur.ch;
  1.2626 +      var idx;
  1.2627 +      for (var i = 0; i < repeat; i ++) {
  1.2628 +        var line = cm.getLine(cur.line);
  1.2629 +        idx = charIdxInLine(start, line, character, forward, true);
  1.2630 +        if (idx == -1) {
  1.2631 +          return null;
  1.2632 +        }
  1.2633 +        start = idx;
  1.2634 +      }
  1.2635 +      return Pos(cm.getCursor().line, idx);
  1.2636 +    }
  1.2637 +
  1.2638 +    function moveToColumn(cm, repeat) {
  1.2639 +      // repeat is always >= 1, so repeat - 1 always corresponds
  1.2640 +      // to the column we want to go to.
  1.2641 +      var line = cm.getCursor().line;
  1.2642 +      return clipCursorToContent(cm, Pos(line, repeat - 1));
  1.2643 +    }
  1.2644 +
  1.2645 +    function updateMark(cm, vim, markName, pos) {
  1.2646 +      if (!inArray(markName, validMarks)) {
  1.2647 +        return;
  1.2648 +      }
  1.2649 +      if (vim.marks[markName]) {
  1.2650 +        vim.marks[markName].clear();
  1.2651 +      }
  1.2652 +      vim.marks[markName] = cm.setBookmark(pos);
  1.2653 +    }
  1.2654 +
  1.2655 +    function charIdxInLine(start, line, character, forward, includeChar) {
  1.2656 +      // Search for char in line.
  1.2657 +      // motion_options: {forward, includeChar}
  1.2658 +      // If includeChar = true, include it too.
  1.2659 +      // If forward = true, search forward, else search backwards.
  1.2660 +      // If char is not found on this line, do nothing
  1.2661 +      var idx;
  1.2662 +      if (forward) {
  1.2663 +        idx = line.indexOf(character, start + 1);
  1.2664 +        if (idx != -1 && !includeChar) {
  1.2665 +          idx -= 1;
  1.2666 +        }
  1.2667 +      } else {
  1.2668 +        idx = line.lastIndexOf(character, start - 1);
  1.2669 +        if (idx != -1 && !includeChar) {
  1.2670 +          idx += 1;
  1.2671 +        }
  1.2672 +      }
  1.2673 +      return idx;
  1.2674 +    }
  1.2675 +
  1.2676 +    function getContextLevel(ctx) {
  1.2677 +      return (ctx === 'string' || ctx === 'comment') ? 1 : 0;
  1.2678 +    }
  1.2679 +
  1.2680 +    function findMatchedSymbol(cm, cur, symb) {
  1.2681 +      var line = cur.line;
  1.2682 +      var ch = cur.ch;
  1.2683 +      symb = symb ? symb : cm.getLine(line).charAt(ch);
  1.2684 +
  1.2685 +      var symbContext = cm.getTokenAt(Pos(line, ch + 1)).type;
  1.2686 +      var symbCtxLevel = getContextLevel(symbContext);
  1.2687 +
  1.2688 +      var reverseSymb = ({
  1.2689 +        '(': ')', ')': '(',
  1.2690 +        '[': ']', ']': '[',
  1.2691 +        '{': '}', '}': '{'})[symb];
  1.2692 +
  1.2693 +      // Couldn't find a matching symbol, abort
  1.2694 +      if (!reverseSymb) {
  1.2695 +        return cur;
  1.2696 +      }
  1.2697 +
  1.2698 +      // set our increment to move forward (+1) or backwards (-1)
  1.2699 +      // depending on which bracket we're matching
  1.2700 +      var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1;
  1.2701 +      var endLine = increment === 1 ? cm.lineCount() : -1;
  1.2702 +      var depth = 1, nextCh = symb, index = ch, lineText = cm.getLine(line);
  1.2703 +      // Simple search for closing paren--just count openings and closings till
  1.2704 +      // we find our match
  1.2705 +      // TODO: use info from CodeMirror to ignore closing brackets in comments
  1.2706 +      // and quotes, etc.
  1.2707 +      while (line !== endLine && depth > 0) {
  1.2708 +        index += increment;
  1.2709 +        nextCh = lineText.charAt(index);
  1.2710 +        if (!nextCh) {
  1.2711 +          line += increment;
  1.2712 +          lineText = cm.getLine(line) || '';
  1.2713 +          if (increment > 0) {
  1.2714 +            index = 0;
  1.2715 +          } else {
  1.2716 +            var lineLen = lineText.length;
  1.2717 +            index = (lineLen > 0) ? (lineLen-1) : 0;
  1.2718 +          }
  1.2719 +          nextCh = lineText.charAt(index);
  1.2720 +        }
  1.2721 +        var revSymbContext = cm.getTokenAt(Pos(line, index + 1)).type;
  1.2722 +        var revSymbCtxLevel = getContextLevel(revSymbContext);
  1.2723 +        if (symbCtxLevel >= revSymbCtxLevel) {
  1.2724 +          if (nextCh === symb) {
  1.2725 +            depth++;
  1.2726 +          } else if (nextCh === reverseSymb) {
  1.2727 +            depth--;
  1.2728 +          }
  1.2729 +        }
  1.2730 +      }
  1.2731 +
  1.2732 +      if (nextCh) {
  1.2733 +        return Pos(line, index);
  1.2734 +      }
  1.2735 +      return cur;
  1.2736 +    }
  1.2737 +
  1.2738 +    // TODO: perhaps this finagling of start and end positions belonds
  1.2739 +    // in codmirror/replaceRange?
  1.2740 +    function selectCompanionObject(cm, revSymb, inclusive) {
  1.2741 +      var cur = copyCursor(cm.getCursor());
  1.2742 +      var end = findMatchedSymbol(cm, cur, revSymb);
  1.2743 +      var start = findMatchedSymbol(cm, end);
  1.2744 +
  1.2745 +      if ((start.line == end.line && start.ch > end.ch)
  1.2746 +          || (start.line > end.line)) {
  1.2747 +        var tmp = start;
  1.2748 +        start = end;
  1.2749 +        end = tmp;
  1.2750 +      }
  1.2751 +
  1.2752 +      if (inclusive) {
  1.2753 +        end.ch += 1;
  1.2754 +      } else {
  1.2755 +        start.ch += 1;
  1.2756 +      }
  1.2757 +
  1.2758 +      return { start: start, end: end };
  1.2759 +    }
  1.2760 +
  1.2761 +    // Takes in a symbol and a cursor and tries to simulate text objects that
  1.2762 +    // have identical opening and closing symbols
  1.2763 +    // TODO support across multiple lines
  1.2764 +    function findBeginningAndEnd(cm, symb, inclusive) {
  1.2765 +      var cur = copyCursor(cm.getCursor());
  1.2766 +      var line = cm.getLine(cur.line);
  1.2767 +      var chars = line.split('');
  1.2768 +      var start, end, i, len;
  1.2769 +      var firstIndex = chars.indexOf(symb);
  1.2770 +
  1.2771 +      // the decision tree is to always look backwards for the beginning first,
  1.2772 +      // but if the cursor is in front of the first instance of the symb,
  1.2773 +      // then move the cursor forward
  1.2774 +      if (cur.ch < firstIndex) {
  1.2775 +        cur.ch = firstIndex;
  1.2776 +        // Why is this line even here???
  1.2777 +        // cm.setCursor(cur.line, firstIndex+1);
  1.2778 +      }
  1.2779 +      // otherwise if the cursor is currently on the closing symbol
  1.2780 +      else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
  1.2781 +        end = cur.ch; // assign end to the current cursor
  1.2782 +        --cur.ch; // make sure to look backwards
  1.2783 +      }
  1.2784 +
  1.2785 +      // if we're currently on the symbol, we've got a start
  1.2786 +      if (chars[cur.ch] == symb && !end) {
  1.2787 +        start = cur.ch + 1; // assign start to ahead of the cursor
  1.2788 +      } else {
  1.2789 +        // go backwards to find the start
  1.2790 +        for (i = cur.ch; i > -1 && !start; i--) {
  1.2791 +          if (chars[i] == symb) {
  1.2792 +            start = i + 1;
  1.2793 +          }
  1.2794 +        }
  1.2795 +      }
  1.2796 +
  1.2797 +      // look forwards for the end symbol
  1.2798 +      if (start && !end) {
  1.2799 +        for (i = start, len = chars.length; i < len && !end; i++) {
  1.2800 +          if (chars[i] == symb) {
  1.2801 +            end = i;
  1.2802 +          }
  1.2803 +        }
  1.2804 +      }
  1.2805 +
  1.2806 +      // nothing found
  1.2807 +      if (!start || !end) {
  1.2808 +        return { start: cur, end: cur };
  1.2809 +      }
  1.2810 +
  1.2811 +      // include the symbols
  1.2812 +      if (inclusive) {
  1.2813 +        --start; ++end;
  1.2814 +      }
  1.2815 +
  1.2816 +      return {
  1.2817 +        start: Pos(cur.line, start),
  1.2818 +        end: Pos(cur.line, end)
  1.2819 +      };
  1.2820 +    }
  1.2821 +
  1.2822 +    // Search functions
  1.2823 +    defineOption('pcre', true, 'boolean');
  1.2824 +    function SearchState() {}
  1.2825 +    SearchState.prototype = {
  1.2826 +      getQuery: function() {
  1.2827 +        return vimGlobalState.query;
  1.2828 +      },
  1.2829 +      setQuery: function(query) {
  1.2830 +        vimGlobalState.query = query;
  1.2831 +      },
  1.2832 +      getOverlay: function() {
  1.2833 +        return this.searchOverlay;
  1.2834 +      },
  1.2835 +      setOverlay: function(overlay) {
  1.2836 +        this.searchOverlay = overlay;
  1.2837 +      },
  1.2838 +      isReversed: function() {
  1.2839 +        return vimGlobalState.isReversed;
  1.2840 +      },
  1.2841 +      setReversed: function(reversed) {
  1.2842 +        vimGlobalState.isReversed = reversed;
  1.2843 +      }
  1.2844 +    };
  1.2845 +    function getSearchState(cm) {
  1.2846 +      var vim = cm.state.vim;
  1.2847 +      return vim.searchState_ || (vim.searchState_ = new SearchState());
  1.2848 +    }
  1.2849 +    function dialog(cm, template, shortText, onClose, options) {
  1.2850 +      if (cm.openDialog) {
  1.2851 +        cm.openDialog(template, onClose, { bottom: true, value: options.value,
  1.2852 +            onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp });
  1.2853 +      }
  1.2854 +      else {
  1.2855 +        onClose(prompt(shortText, ''));
  1.2856 +      }
  1.2857 +    }
  1.2858 +
  1.2859 +    function findUnescapedSlashes(str) {
  1.2860 +      var escapeNextChar = false;
  1.2861 +      var slashes = [];
  1.2862 +      for (var i = 0; i < str.length; i++) {
  1.2863 +        var c = str.charAt(i);
  1.2864 +        if (!escapeNextChar && c == '/') {
  1.2865 +          slashes.push(i);
  1.2866 +        }
  1.2867 +        escapeNextChar = !escapeNextChar && (c == '\\');
  1.2868 +      }
  1.2869 +      return slashes;
  1.2870 +    }
  1.2871 +
  1.2872 +    // Translates a search string from ex (vim) syntax into javascript form.
  1.2873 +    function translateRegex(str) {
  1.2874 +      // When these match, add a '\' if unescaped or remove one if escaped.
  1.2875 +      var specials = ['|', '(', ')', '{'];
  1.2876 +      // Remove, but never add, a '\' for these.
  1.2877 +      var unescape = ['}'];
  1.2878 +      var escapeNextChar = false;
  1.2879 +      var out = [];
  1.2880 +      for (var i = -1; i < str.length; i++) {
  1.2881 +        var c = str.charAt(i) || '';
  1.2882 +        var n = str.charAt(i+1) || '';
  1.2883 +        var specialComesNext = (specials.indexOf(n) != -1);
  1.2884 +        if (escapeNextChar) {
  1.2885 +          if (c !== '\\' || !specialComesNext) {
  1.2886 +            out.push(c);
  1.2887 +          }
  1.2888 +          escapeNextChar = false;
  1.2889 +        } else {
  1.2890 +          if (c === '\\') {
  1.2891 +            escapeNextChar = true;
  1.2892 +            // Treat the unescape list as special for removing, but not adding '\'.
  1.2893 +            if (unescape.indexOf(n) != -1) {
  1.2894 +              specialComesNext = true;
  1.2895 +            }
  1.2896 +            // Not passing this test means removing a '\'.
  1.2897 +            if (!specialComesNext || n === '\\') {
  1.2898 +              out.push(c);
  1.2899 +            }
  1.2900 +          } else {
  1.2901 +            out.push(c);
  1.2902 +            if (specialComesNext && n !== '\\') {
  1.2903 +              out.push('\\');
  1.2904 +            }
  1.2905 +          }
  1.2906 +        }
  1.2907 +      }
  1.2908 +      return out.join('');
  1.2909 +    }
  1.2910 +
  1.2911 +    // Translates the replace part of a search and replace from ex (vim) syntax into
  1.2912 +    // javascript form.  Similar to translateRegex, but additionally fixes back references
  1.2913 +    // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'.
  1.2914 +    function translateRegexReplace(str) {
  1.2915 +      var escapeNextChar = false;
  1.2916 +      var out = [];
  1.2917 +      for (var i = -1; i < str.length; i++) {
  1.2918 +        var c = str.charAt(i) || '';
  1.2919 +        var n = str.charAt(i+1) || '';
  1.2920 +        if (escapeNextChar) {
  1.2921 +          // At any point in the loop, escapeNextChar is true if the previous
  1.2922 +          // character was a '\' and was not escaped.
  1.2923 +          out.push(c);
  1.2924 +          escapeNextChar = false;
  1.2925 +        } else {
  1.2926 +          if (c === '\\') {
  1.2927 +            escapeNextChar = true;
  1.2928 +            if ((isNumber(n) || n === '$')) {
  1.2929 +              out.push('$');
  1.2930 +            } else if (n !== '/' && n !== '\\') {
  1.2931 +              out.push('\\');
  1.2932 +            }
  1.2933 +          } else {
  1.2934 +            if (c === '$') {
  1.2935 +              out.push('$');
  1.2936 +            }
  1.2937 +            out.push(c);
  1.2938 +            if (n === '/') {
  1.2939 +              out.push('\\');
  1.2940 +            }
  1.2941 +          }
  1.2942 +        }
  1.2943 +      }
  1.2944 +      return out.join('');
  1.2945 +    }
  1.2946 +
  1.2947 +    // Unescape \ and / in the replace part, for PCRE mode.
  1.2948 +    function unescapeRegexReplace(str) {
  1.2949 +      var stream = new CodeMirror.StringStream(str);
  1.2950 +      var output = [];
  1.2951 +      while (!stream.eol()) {
  1.2952 +        // Search for \.
  1.2953 +        while (stream.peek() && stream.peek() != '\\') {
  1.2954 +          output.push(stream.next());
  1.2955 +        }
  1.2956 +        if (stream.match('\\/', true)) {
  1.2957 +          // \/ => /
  1.2958 +          output.push('/');
  1.2959 +        } else if (stream.match('\\\\', true)) {
  1.2960 +          // \\ => \
  1.2961 +          output.push('\\');
  1.2962 +        } else {
  1.2963 +          // Don't change anything
  1.2964 +          output.push(stream.next());
  1.2965 +        }
  1.2966 +      }
  1.2967 +      return output.join('');
  1.2968 +    }
  1.2969 +
  1.2970 +    /**
  1.2971 +     * Extract the regular expression from the query and return a Regexp object.
  1.2972 +     * Returns null if the query is blank.
  1.2973 +     * If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
  1.2974 +     * If smartCase is passed in, and the query contains upper case letters,
  1.2975 +     *   then ignoreCase is overridden, and the 'i' flag will not be set.
  1.2976 +     * If the query contains the /i in the flag part of the regular expression,
  1.2977 +     *   then both ignoreCase and smartCase are ignored, and 'i' will be passed
  1.2978 +     *   through to the Regex object.
  1.2979 +     */
  1.2980 +    function parseQuery(query, ignoreCase, smartCase) {
  1.2981 +      // Check if the query is already a regex.
  1.2982 +      if (query instanceof RegExp) { return query; }
  1.2983 +      // First try to extract regex + flags from the input. If no flags found,
  1.2984 +      // extract just the regex. IE does not accept flags directly defined in
  1.2985 +      // the regex string in the form /regex/flags
  1.2986 +      var slashes = findUnescapedSlashes(query);
  1.2987 +      var regexPart;
  1.2988 +      var forceIgnoreCase;
  1.2989 +      if (!slashes.length) {
  1.2990 +        // Query looks like 'regexp'
  1.2991 +        regexPart = query;
  1.2992 +      } else {
  1.2993 +        // Query looks like 'regexp/...'
  1.2994 +        regexPart = query.substring(0, slashes[0]);
  1.2995 +        var flagsPart = query.substring(slashes[0]);
  1.2996 +        forceIgnoreCase = (flagsPart.indexOf('i') != -1);
  1.2997 +      }
  1.2998 +      if (!regexPart) {
  1.2999 +        return null;
  1.3000 +      }
  1.3001 +      if (!getOption('pcre')) {
  1.3002 +        regexPart = translateRegex(regexPart);
  1.3003 +      }
  1.3004 +      if (smartCase) {
  1.3005 +        ignoreCase = (/^[^A-Z]*$/).test(regexPart);
  1.3006 +      }
  1.3007 +      var regexp = new RegExp(regexPart,
  1.3008 +          (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
  1.3009 +      return regexp;
  1.3010 +    }
  1.3011 +    function showConfirm(cm, text) {
  1.3012 +      if (cm.openNotification) {
  1.3013 +        cm.openNotification('<span style="color: red">' + text + '</span>',
  1.3014 +                            {bottom: true, duration: 5000});
  1.3015 +      } else {
  1.3016 +        alert(text);
  1.3017 +      }
  1.3018 +    }
  1.3019 +    function makePrompt(prefix, desc) {
  1.3020 +      var raw = '';
  1.3021 +      if (prefix) {
  1.3022 +        raw += '<span style="font-family: monospace">' + prefix + '</span>';
  1.3023 +      }
  1.3024 +      raw += '<input type="text"/> ' +
  1.3025 +          '<span style="color: #888">';
  1.3026 +      if (desc) {
  1.3027 +        raw += '<span style="color: #888">';
  1.3028 +        raw += desc;
  1.3029 +        raw += '</span>';
  1.3030 +      }
  1.3031 +      return raw;
  1.3032 +    }
  1.3033 +    var searchPromptDesc = '(Javascript regexp)';
  1.3034 +    function showPrompt(cm, options) {
  1.3035 +      var shortText = (options.prefix || '') + ' ' + (options.desc || '');
  1.3036 +      var prompt = makePrompt(options.prefix, options.desc);
  1.3037 +      dialog(cm, prompt, shortText, options.onClose, options);
  1.3038 +    }
  1.3039 +    function regexEqual(r1, r2) {
  1.3040 +      if (r1 instanceof RegExp && r2 instanceof RegExp) {
  1.3041 +          var props = ['global', 'multiline', 'ignoreCase', 'source'];
  1.3042 +          for (var i = 0; i < props.length; i++) {
  1.3043 +              var prop = props[i];
  1.3044 +              if (r1[prop] !== r2[prop]) {
  1.3045 +                  return false;
  1.3046 +              }
  1.3047 +          }
  1.3048 +          return true;
  1.3049 +      }
  1.3050 +      return false;
  1.3051 +    }
  1.3052 +    // Returns true if the query is valid.
  1.3053 +    function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
  1.3054 +      if (!rawQuery) {
  1.3055 +        return;
  1.3056 +      }
  1.3057 +      var state = getSearchState(cm);
  1.3058 +      var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);
  1.3059 +      if (!query) {
  1.3060 +        return;
  1.3061 +      }
  1.3062 +      highlightSearchMatches(cm, query);
  1.3063 +      if (regexEqual(query, state.getQuery())) {
  1.3064 +        return query;
  1.3065 +      }
  1.3066 +      state.setQuery(query);
  1.3067 +      return query;
  1.3068 +    }
  1.3069 +    function searchOverlay(query) {
  1.3070 +      if (query.source.charAt(0) == '^') {
  1.3071 +        var matchSol = true;
  1.3072 +      }
  1.3073 +      return {
  1.3074 +        token: function(stream) {
  1.3075 +          if (matchSol && !stream.sol()) {
  1.3076 +            stream.skipToEnd();
  1.3077 +            return;
  1.3078 +          }
  1.3079 +          var match = stream.match(query, false);
  1.3080 +          if (match) {
  1.3081 +            if (match[0].length == 0) {
  1.3082 +              // Matched empty string, skip to next.
  1.3083 +              stream.next();
  1.3084 +              return 'searching';
  1.3085 +            }
  1.3086 +            if (!stream.sol()) {
  1.3087 +              // Backtrack 1 to match \b
  1.3088 +              stream.backUp(1);
  1.3089 +              if (!query.exec(stream.next() + match[0])) {
  1.3090 +                stream.next();
  1.3091 +                return null;
  1.3092 +              }
  1.3093 +            }
  1.3094 +            stream.match(query);
  1.3095 +            return 'searching';
  1.3096 +          }
  1.3097 +          while (!stream.eol()) {
  1.3098 +            stream.next();
  1.3099 +            if (stream.match(query, false)) break;
  1.3100 +          }
  1.3101 +        },
  1.3102 +        query: query
  1.3103 +      };
  1.3104 +    }
  1.3105 +    function highlightSearchMatches(cm, query) {
  1.3106 +      var overlay = getSearchState(cm).getOverlay();
  1.3107 +      if (!overlay || query != overlay.query) {
  1.3108 +        if (overlay) {
  1.3109 +          cm.removeOverlay(overlay);
  1.3110 +        }
  1.3111 +        overlay = searchOverlay(query);
  1.3112 +        cm.addOverlay(overlay);
  1.3113 +        getSearchState(cm).setOverlay(overlay);
  1.3114 +      }
  1.3115 +    }
  1.3116 +    function findNext(cm, prev, query, repeat) {
  1.3117 +      if (repeat === undefined) { repeat = 1; }
  1.3118 +      return cm.operation(function() {
  1.3119 +        var pos = cm.getCursor();
  1.3120 +        var cursor = cm.getSearchCursor(query, pos);
  1.3121 +        for (var i = 0; i < repeat; i++) {
  1.3122 +          var found = cursor.find(prev);
  1.3123 +          if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); }
  1.3124 +          if (!found) {
  1.3125 +            // SearchCursor may have returned null because it hit EOF, wrap
  1.3126 +            // around and try again.
  1.3127 +            cursor = cm.getSearchCursor(query,
  1.3128 +                (prev) ? Pos(cm.lastLine()) : Pos(cm.firstLine(), 0) );
  1.3129 +            if (!cursor.find(prev)) {
  1.3130 +              return;
  1.3131 +            }
  1.3132 +          }
  1.3133 +        }
  1.3134 +        return cursor.from();
  1.3135 +      });
  1.3136 +    }
  1.3137 +    function clearSearchHighlight(cm) {
  1.3138 +      cm.removeOverlay(getSearchState(cm).getOverlay());
  1.3139 +      getSearchState(cm).setOverlay(null);
  1.3140 +    }
  1.3141 +    /**
  1.3142 +     * Check if pos is in the specified range, INCLUSIVE.
  1.3143 +     * Range can be specified with 1 or 2 arguments.
  1.3144 +     * If the first range argument is an array, treat it as an array of line
  1.3145 +     * numbers. Match pos against any of the lines.
  1.3146 +     * If the first range argument is a number,
  1.3147 +     *   if there is only 1 range argument, check if pos has the same line
  1.3148 +     *       number
  1.3149 +     *   if there are 2 range arguments, then check if pos is in between the two
  1.3150 +     *       range arguments.
  1.3151 +     */
  1.3152 +    function isInRange(pos, start, end) {
  1.3153 +      if (typeof pos != 'number') {
  1.3154 +        // Assume it is a cursor position. Get the line number.
  1.3155 +        pos = pos.line;
  1.3156 +      }
  1.3157 +      if (start instanceof Array) {
  1.3158 +        return inArray(pos, start);
  1.3159 +      } else {
  1.3160 +        if (end) {
  1.3161 +          return (pos >= start && pos <= end);
  1.3162 +        } else {
  1.3163 +          return pos == start;
  1.3164 +        }
  1.3165 +      }
  1.3166 +    }
  1.3167 +    function getUserVisibleLines(cm) {
  1.3168 +      var scrollInfo = cm.getScrollInfo();
  1.3169 +      var occludeToleranceTop = 6;
  1.3170 +      var occludeToleranceBottom = 10;
  1.3171 +      var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
  1.3172 +      var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
  1.3173 +      var to = cm.coordsChar({left:0, top: bottomY}, 'local');
  1.3174 +      return {top: from.line, bottom: to.line};
  1.3175 +    }
  1.3176 +
  1.3177 +    // Ex command handling
  1.3178 +    // Care must be taken when adding to the default Ex command map. For any
  1.3179 +    // pair of commands that have a shared prefix, at least one of their
  1.3180 +    // shortNames must not match the prefix of the other command.
  1.3181 +    var defaultExCommandMap = [
  1.3182 +      { name: 'map' },
  1.3183 +      { name: 'nmap', shortName: 'nm' },
  1.3184 +      { name: 'vmap', shortName: 'vm' },
  1.3185 +      { name: 'unmap' },
  1.3186 +      { name: 'write', shortName: 'w' },
  1.3187 +      { name: 'undo', shortName: 'u' },
  1.3188 +      { name: 'redo', shortName: 'red' },
  1.3189 +      { name: 'set', shortName: 'set' },
  1.3190 +      { name: 'sort', shortName: 'sor' },
  1.3191 +      { name: 'substitute', shortName: 's' },
  1.3192 +      { name: 'nohlsearch', shortName: 'noh' },
  1.3193 +      { name: 'delmarks', shortName: 'delm' },
  1.3194 +      { name: 'registers', shortName: 'reg' }
  1.3195 +    ];
  1.3196 +    Vim.ExCommandDispatcher = function() {
  1.3197 +      this.buildCommandMap_();
  1.3198 +    };
  1.3199 +    Vim.ExCommandDispatcher.prototype = {
  1.3200 +      processCommand: function(cm, input) {
  1.3201 +        var vim = cm.state.vim;
  1.3202 +        if (vim.visualMode) {
  1.3203 +          exitVisualMode(cm);
  1.3204 +        }
  1.3205 +        var inputStream = new CodeMirror.StringStream(input);
  1.3206 +        var params = {};
  1.3207 +        params.input = input;
  1.3208 +        try {
  1.3209 +          this.parseInput_(cm, inputStream, params);
  1.3210 +        } catch(e) {
  1.3211 +          showConfirm(cm, e);
  1.3212 +          throw e;
  1.3213 +        }
  1.3214 +        var commandName;
  1.3215 +        if (!params.commandName) {
  1.3216 +          // If only a line range is defined, move to the line.
  1.3217 +          if (params.line !== undefined) {
  1.3218 +            commandName = 'move';
  1.3219 +          }
  1.3220 +        } else {
  1.3221 +          var command = this.matchCommand_(params.commandName);
  1.3222 +          if (command) {
  1.3223 +            commandName = command.name;
  1.3224 +            this.parseCommandArgs_(inputStream, params, command);
  1.3225 +            if (command.type == 'exToKey') {
  1.3226 +              // Handle Ex to Key mapping.
  1.3227 +              for (var i = 0; i < command.toKeys.length; i++) {
  1.3228 +                CodeMirror.Vim.handleKey(cm, command.toKeys[i]);
  1.3229 +              }
  1.3230 +              return;
  1.3231 +            } else if (command.type == 'exToEx') {
  1.3232 +              // Handle Ex to Ex mapping.
  1.3233 +              this.processCommand(cm, command.toInput);
  1.3234 +              return;
  1.3235 +            }
  1.3236 +          }
  1.3237 +        }
  1.3238 +        if (!commandName) {
  1.3239 +          showConfirm(cm, 'Not an editor command ":' + input + '"');
  1.3240 +          return;
  1.3241 +        }
  1.3242 +        try {
  1.3243 +          exCommands[commandName](cm, params);
  1.3244 +        } catch(e) {
  1.3245 +          showConfirm(cm, e);
  1.3246 +          throw e;
  1.3247 +        }
  1.3248 +      },
  1.3249 +      parseInput_: function(cm, inputStream, result) {
  1.3250 +        inputStream.eatWhile(':');
  1.3251 +        // Parse range.
  1.3252 +        if (inputStream.eat('%')) {
  1.3253 +          result.line = cm.firstLine();
  1.3254 +          result.lineEnd = cm.lastLine();
  1.3255 +        } else {
  1.3256 +          result.line = this.parseLineSpec_(cm, inputStream);
  1.3257 +          if (result.line !== undefined && inputStream.eat(',')) {
  1.3258 +            result.lineEnd = this.parseLineSpec_(cm, inputStream);
  1.3259 +          }
  1.3260 +        }
  1.3261 +
  1.3262 +        // Parse command name.
  1.3263 +        var commandMatch = inputStream.match(/^(\w+)/);
  1.3264 +        if (commandMatch) {
  1.3265 +          result.commandName = commandMatch[1];
  1.3266 +        } else {
  1.3267 +          result.commandName = inputStream.match(/.*/)[0];
  1.3268 +        }
  1.3269 +
  1.3270 +        return result;
  1.3271 +      },
  1.3272 +      parseLineSpec_: function(cm, inputStream) {
  1.3273 +        var numberMatch = inputStream.match(/^(\d+)/);
  1.3274 +        if (numberMatch) {
  1.3275 +          return parseInt(numberMatch[1], 10) - 1;
  1.3276 +        }
  1.3277 +        switch (inputStream.next()) {
  1.3278 +          case '.':
  1.3279 +            return cm.getCursor().line;
  1.3280 +          case '$':
  1.3281 +            return cm.lastLine();
  1.3282 +          case '\'':
  1.3283 +            var mark = cm.state.vim.marks[inputStream.next()];
  1.3284 +            if (mark && mark.find()) {
  1.3285 +              return mark.find().line;
  1.3286 +            }
  1.3287 +            throw new Error('Mark not set');
  1.3288 +          default:
  1.3289 +            inputStream.backUp(1);
  1.3290 +            return undefined;
  1.3291 +        }
  1.3292 +      },
  1.3293 +      parseCommandArgs_: function(inputStream, params, command) {
  1.3294 +        if (inputStream.eol()) {
  1.3295 +          return;
  1.3296 +        }
  1.3297 +        params.argString = inputStream.match(/.*/)[0];
  1.3298 +        // Parse command-line arguments
  1.3299 +        var delim = command.argDelimiter || /\s+/;
  1.3300 +        var args = trim(params.argString).split(delim);
  1.3301 +        if (args.length && args[0]) {
  1.3302 +          params.args = args;
  1.3303 +        }
  1.3304 +      },
  1.3305 +      matchCommand_: function(commandName) {
  1.3306 +        // Return the command in the command map that matches the shortest
  1.3307 +        // prefix of the passed in command name. The match is guaranteed to be
  1.3308 +        // unambiguous if the defaultExCommandMap's shortNames are set up
  1.3309 +        // correctly. (see @code{defaultExCommandMap}).
  1.3310 +        for (var i = commandName.length; i > 0; i--) {
  1.3311 +          var prefix = commandName.substring(0, i);
  1.3312 +          if (this.commandMap_[prefix]) {
  1.3313 +            var command = this.commandMap_[prefix];
  1.3314 +            if (command.name.indexOf(commandName) === 0) {
  1.3315 +              return command;
  1.3316 +            }
  1.3317 +          }
  1.3318 +        }
  1.3319 +        return null;
  1.3320 +      },
  1.3321 +      buildCommandMap_: function() {
  1.3322 +        this.commandMap_ = {};
  1.3323 +        for (var i = 0; i < defaultExCommandMap.length; i++) {
  1.3324 +          var command = defaultExCommandMap[i];
  1.3325 +          var key = command.shortName || command.name;
  1.3326 +          this.commandMap_[key] = command;
  1.3327 +        }
  1.3328 +      },
  1.3329 +      map: function(lhs, rhs, ctx) {
  1.3330 +        if (lhs != ':' && lhs.charAt(0) == ':') {
  1.3331 +          if (ctx) { throw Error('Mode not supported for ex mappings'); }
  1.3332 +          var commandName = lhs.substring(1);
  1.3333 +          if (rhs != ':' && rhs.charAt(0) == ':') {
  1.3334 +            // Ex to Ex mapping
  1.3335 +            this.commandMap_[commandName] = {
  1.3336 +              name: commandName,
  1.3337 +              type: 'exToEx',
  1.3338 +              toInput: rhs.substring(1),
  1.3339 +              user: true
  1.3340 +            };
  1.3341 +          } else {
  1.3342 +            // Ex to key mapping
  1.3343 +            this.commandMap_[commandName] = {
  1.3344 +              name: commandName,
  1.3345 +              type: 'exToKey',
  1.3346 +              toKeys: parseKeyString(rhs),
  1.3347 +              user: true
  1.3348 +            };
  1.3349 +          }
  1.3350 +        } else {
  1.3351 +          if (rhs != ':' && rhs.charAt(0) == ':') {
  1.3352 +            // Key to Ex mapping.
  1.3353 +            var mapping = {
  1.3354 +              keys: parseKeyString(lhs),
  1.3355 +              type: 'keyToEx',
  1.3356 +              exArgs: { input: rhs.substring(1) },
  1.3357 +              user: true};
  1.3358 +            if (ctx) { mapping.context = ctx; }
  1.3359 +            defaultKeymap.unshift(mapping);
  1.3360 +          } else {
  1.3361 +            // Key to key mapping
  1.3362 +            var mapping = {
  1.3363 +              keys: parseKeyString(lhs),
  1.3364 +              type: 'keyToKey',
  1.3365 +              toKeys: parseKeyString(rhs),
  1.3366 +              user: true
  1.3367 +            };
  1.3368 +            if (ctx) { mapping.context = ctx; }
  1.3369 +            defaultKeymap.unshift(mapping);
  1.3370 +          }
  1.3371 +        }
  1.3372 +      },
  1.3373 +      unmap: function(lhs, ctx) {
  1.3374 +        var arrayEquals = function(a, b) {
  1.3375 +          if (a === b) return true;
  1.3376 +          if (a == null || b == null) return true;
  1.3377 +          if (a.length != b.length) return false;
  1.3378 +          for (var i = 0; i < a.length; i++) {
  1.3379 +            if (a[i] !== b[i]) return false;
  1.3380 +          }
  1.3381 +          return true;
  1.3382 +        };
  1.3383 +        if (lhs != ':' && lhs.charAt(0) == ':') {
  1.3384 +          // Ex to Ex or Ex to key mapping
  1.3385 +          if (ctx) { throw Error('Mode not supported for ex mappings'); }
  1.3386 +          var commandName = lhs.substring(1);
  1.3387 +          if (this.commandMap_[commandName] && this.commandMap_[commandName].user) {
  1.3388 +            delete this.commandMap_[commandName];
  1.3389 +            return;
  1.3390 +          }
  1.3391 +        } else {
  1.3392 +          // Key to Ex or key to key mapping
  1.3393 +          var keys = parseKeyString(lhs);
  1.3394 +          for (var i = 0; i < defaultKeymap.length; i++) {
  1.3395 +            if (arrayEquals(keys, defaultKeymap[i].keys)
  1.3396 +                && defaultKeymap[i].context === ctx
  1.3397 +                && defaultKeymap[i].user) {
  1.3398 +              defaultKeymap.splice(i, 1);
  1.3399 +              return;
  1.3400 +            }
  1.3401 +          }
  1.3402 +        }
  1.3403 +        throw Error('No such mapping.');
  1.3404 +      }
  1.3405 +    };
  1.3406 +
  1.3407 +    // Converts a key string sequence of the form a<C-w>bd<Left> into Vim's
  1.3408 +    // keymap representation.
  1.3409 +    function parseKeyString(str) {
  1.3410 +      var key, match;
  1.3411 +      var keys = [];
  1.3412 +      while (str) {
  1.3413 +        match = (/<\w+-.+?>|<\w+>|./).exec(str);
  1.3414 +        if (match === null)break;
  1.3415 +        key = match[0];
  1.3416 +        str = str.substring(match.index + key.length);
  1.3417 +        keys.push(key);
  1.3418 +      }
  1.3419 +      return keys;
  1.3420 +    }
  1.3421 +
  1.3422 +    var exCommands = {
  1.3423 +      map: function(cm, params, ctx) {
  1.3424 +        var mapArgs = params.args;
  1.3425 +        if (!mapArgs || mapArgs.length < 2) {
  1.3426 +          if (cm) {
  1.3427 +            showConfirm(cm, 'Invalid mapping: ' + params.input);
  1.3428 +          }
  1.3429 +          return;
  1.3430 +        }
  1.3431 +        exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx);
  1.3432 +      },
  1.3433 +      nmap: function(cm, params) { this.map(cm, params, 'normal'); },
  1.3434 +      vmap: function(cm, params) { this.map(cm, params, 'visual'); },
  1.3435 +      unmap: function(cm, params, ctx) {
  1.3436 +        var mapArgs = params.args;
  1.3437 +        if (!mapArgs || mapArgs.length < 1) {
  1.3438 +          if (cm) {
  1.3439 +            showConfirm(cm, 'No such mapping: ' + params.input);
  1.3440 +          }
  1.3441 +          return;
  1.3442 +        }
  1.3443 +        exCommandDispatcher.unmap(mapArgs[0], ctx);
  1.3444 +      },
  1.3445 +      move: function(cm, params) {
  1.3446 +        commandDispatcher.processCommand(cm, cm.state.vim, {
  1.3447 +            type: 'motion',
  1.3448 +            motion: 'moveToLineOrEdgeOfDocument',
  1.3449 +            motionArgs: { forward: false, explicitRepeat: true,
  1.3450 +              linewise: true },
  1.3451 +            repeatOverride: params.line+1});
  1.3452 +      },
  1.3453 +      set: function(cm, params) {
  1.3454 +        var setArgs = params.args;
  1.3455 +        if (!setArgs || setArgs.length < 1) {
  1.3456 +          if (cm) {
  1.3457 +            showConfirm(cm, 'Invalid mapping: ' + params.input);
  1.3458 +          }
  1.3459 +          return;
  1.3460 +        }
  1.3461 +        var expr = setArgs[0].split('=');
  1.3462 +        var optionName = expr[0];
  1.3463 +        var value = expr[1];
  1.3464 +        var forceGet = false;
  1.3465 +
  1.3466 +        if (optionName.charAt(optionName.length - 1) == '?') {
  1.3467 +          // If post-fixed with ?, then the set is actually a get.
  1.3468 +          if (value) { throw Error('Trailing characters: ' + params.argString); }
  1.3469 +          optionName = optionName.substring(0, optionName.length - 1);
  1.3470 +          forceGet = true;
  1.3471 +        }
  1.3472 +        if (value === undefined && optionName.substring(0, 2) == 'no') {
  1.3473 +          // To set boolean options to false, the option name is prefixed with
  1.3474 +          // 'no'.
  1.3475 +          optionName = optionName.substring(2);
  1.3476 +          value = false;
  1.3477 +        }
  1.3478 +        var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean';
  1.3479 +        if (optionIsBoolean && value == undefined) {
  1.3480 +          // Calling set with a boolean option sets it to true.
  1.3481 +          value = true;
  1.3482 +        }
  1.3483 +        if (!optionIsBoolean && !value || forceGet) {
  1.3484 +          var oldValue = getOption(optionName);
  1.3485 +          // If no value is provided, then we assume this is a get.
  1.3486 +          if (oldValue === true || oldValue === false) {
  1.3487 +            showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName);
  1.3488 +          } else {
  1.3489 +            showConfirm(cm, '  ' + optionName + '=' + oldValue);
  1.3490 +          }
  1.3491 +        } else {
  1.3492 +          setOption(optionName, value);
  1.3493 +        }
  1.3494 +      },
  1.3495 +      registers: function(cm,params) {
  1.3496 +        var regArgs = params.args;
  1.3497 +        var registers = vimGlobalState.registerController.registers;
  1.3498 +        var regInfo = '----------Registers----------<br><br>';
  1.3499 +        if (!regArgs) {
  1.3500 +          for (var registerName in registers) {
  1.3501 +            var text = registers[registerName].toString();
  1.3502 +            if (text.length) {
  1.3503 +              regInfo += '"' + registerName + '    ' + text + '<br>';
  1.3504 +            }
  1.3505 +          }
  1.3506 +        } else {
  1.3507 +          var registerName;
  1.3508 +          regArgs = regArgs.join('');
  1.3509 +          for (var i = 0; i < regArgs.length; i++) {
  1.3510 +            registerName = regArgs.charAt(i);
  1.3511 +            if (!vimGlobalState.registerController.isValidRegister(registerName)) {
  1.3512 +              continue;
  1.3513 +            }
  1.3514 +            var register = registers[registerName] || new Register();
  1.3515 +            regInfo += '"' + registerName + '    ' + register.text + '<br>';
  1.3516 +          }
  1.3517 +        }
  1.3518 +        showConfirm(cm, regInfo);
  1.3519 +      },
  1.3520 +      sort: function(cm, params) {
  1.3521 +        var reverse, ignoreCase, unique, number;
  1.3522 +        function parseArgs() {
  1.3523 +          if (params.argString) {
  1.3524 +            var args = new CodeMirror.StringStream(params.argString);
  1.3525 +            if (args.eat('!')) { reverse = true; }
  1.3526 +            if (args.eol()) { return; }
  1.3527 +            if (!args.eatSpace()) { return 'Invalid arguments'; }
  1.3528 +            var opts = args.match(/[a-z]+/);
  1.3529 +            if (opts) {
  1.3530 +              opts = opts[0];
  1.3531 +              ignoreCase = opts.indexOf('i') != -1;
  1.3532 +              unique = opts.indexOf('u') != -1;
  1.3533 +              var decimal = opts.indexOf('d') != -1 && 1;
  1.3534 +              var hex = opts.indexOf('x') != -1 && 1;
  1.3535 +              var octal = opts.indexOf('o') != -1 && 1;
  1.3536 +              if (decimal + hex + octal > 1) { return 'Invalid arguments'; }
  1.3537 +              number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
  1.3538 +            }
  1.3539 +            if (args.eatSpace() && args.match(/\/.*\//)) { 'patterns not supported'; }
  1.3540 +          }
  1.3541 +        }
  1.3542 +        var err = parseArgs();
  1.3543 +        if (err) {
  1.3544 +          showConfirm(cm, err + ': ' + params.argString);
  1.3545 +          return;
  1.3546 +        }
  1.3547 +        var lineStart = params.line || cm.firstLine();
  1.3548 +        var lineEnd = params.lineEnd || params.line || cm.lastLine();
  1.3549 +        if (lineStart == lineEnd) { return; }
  1.3550 +        var curStart = Pos(lineStart, 0);
  1.3551 +        var curEnd = Pos(lineEnd, lineLength(cm, lineEnd));
  1.3552 +        var text = cm.getRange(curStart, curEnd).split('\n');
  1.3553 +        var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ :
  1.3554 +           (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :
  1.3555 +           (number == 'octal') ? /([0-7]+)/ : null;
  1.3556 +        var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;
  1.3557 +        var numPart = [], textPart = [];
  1.3558 +        if (number) {
  1.3559 +          for (var i = 0; i < text.length; i++) {
  1.3560 +            if (numberRegex.exec(text[i])) {
  1.3561 +              numPart.push(text[i]);
  1.3562 +            } else {
  1.3563 +              textPart.push(text[i]);
  1.3564 +            }
  1.3565 +          }
  1.3566 +        } else {
  1.3567 +          textPart = text;
  1.3568 +        }
  1.3569 +        function compareFn(a, b) {
  1.3570 +          if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
  1.3571 +          if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }
  1.3572 +          var anum = number && numberRegex.exec(a);
  1.3573 +          var bnum = number && numberRegex.exec(b);
  1.3574 +          if (!anum) { return a < b ? -1 : 1; }
  1.3575 +          anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);
  1.3576 +          bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);
  1.3577 +          return anum - bnum;
  1.3578 +        }
  1.3579 +        numPart.sort(compareFn);
  1.3580 +        textPart.sort(compareFn);
  1.3581 +        text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);
  1.3582 +        if (unique) { // Remove duplicate lines
  1.3583 +          var textOld = text;
  1.3584 +          var lastLine;
  1.3585 +          text = [];
  1.3586 +          for (var i = 0; i < textOld.length; i++) {
  1.3587 +            if (textOld[i] != lastLine) {
  1.3588 +              text.push(textOld[i]);
  1.3589 +            }
  1.3590 +            lastLine = textOld[i];
  1.3591 +          }
  1.3592 +        }
  1.3593 +        cm.replaceRange(text.join('\n'), curStart, curEnd);
  1.3594 +      },
  1.3595 +      substitute: function(cm, params) {
  1.3596 +        if (!cm.getSearchCursor) {
  1.3597 +          throw new Error('Search feature not available. Requires searchcursor.js or ' +
  1.3598 +              'any other getSearchCursor implementation.');
  1.3599 +        }
  1.3600 +        var argString = params.argString;
  1.3601 +        var slashes = findUnescapedSlashes(argString);
  1.3602 +        if (slashes[0] !== 0) {
  1.3603 +          showConfirm(cm, 'Substitutions should be of the form ' +
  1.3604 +              ':s/pattern/replace/');
  1.3605 +          return;
  1.3606 +        }
  1.3607 +        var regexPart = argString.substring(slashes[0] + 1, slashes[1]);
  1.3608 +        var replacePart = '';
  1.3609 +        var flagsPart;
  1.3610 +        var count;
  1.3611 +        var confirm = false; // Whether to confirm each replace.
  1.3612 +        if (slashes[1]) {
  1.3613 +          replacePart = argString.substring(slashes[1] + 1, slashes[2]);
  1.3614 +          if (getOption('pcre')) {
  1.3615 +            replacePart = unescapeRegexReplace(replacePart);
  1.3616 +          } else {
  1.3617 +            replacePart = translateRegexReplace(replacePart);
  1.3618 +          }
  1.3619 +        }
  1.3620 +        if (slashes[2]) {
  1.3621 +          // After the 3rd slash, we can have flags followed by a space followed
  1.3622 +          // by count.
  1.3623 +          var trailing = argString.substring(slashes[2] + 1).split(' ');
  1.3624 +          flagsPart = trailing[0];
  1.3625 +          count = parseInt(trailing[1]);
  1.3626 +        }
  1.3627 +        if (flagsPart) {
  1.3628 +          if (flagsPart.indexOf('c') != -1) {
  1.3629 +            confirm = true;
  1.3630 +            flagsPart.replace('c', '');
  1.3631 +          }
  1.3632 +          regexPart = regexPart + '/' + flagsPart;
  1.3633 +        }
  1.3634 +        if (regexPart) {
  1.3635 +          // If regex part is empty, then use the previous query. Otherwise use
  1.3636 +          // the regex part as the new query.
  1.3637 +          try {
  1.3638 +            updateSearchQuery(cm, regexPart, true /** ignoreCase */,
  1.3639 +              true /** smartCase */);
  1.3640 +          } catch (e) {
  1.3641 +            showConfirm(cm, 'Invalid regex: ' + regexPart);
  1.3642 +            return;
  1.3643 +          }
  1.3644 +        }
  1.3645 +        var state = getSearchState(cm);
  1.3646 +        var query = state.getQuery();
  1.3647 +        var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
  1.3648 +        var lineEnd = params.lineEnd || lineStart;
  1.3649 +        if (count) {
  1.3650 +          lineStart = lineEnd;
  1.3651 +          lineEnd = lineStart + count - 1;
  1.3652 +        }
  1.3653 +        var startPos = clipCursorToContent(cm, Pos(lineStart, 0));
  1.3654 +        var cursor = cm.getSearchCursor(query, startPos);
  1.3655 +        doReplace(cm, confirm, lineStart, lineEnd, cursor, query, replacePart);
  1.3656 +      },
  1.3657 +      redo: CodeMirror.commands.redo,
  1.3658 +      undo: CodeMirror.commands.undo,
  1.3659 +      write: function(cm) {
  1.3660 +        if (CodeMirror.commands.save) {
  1.3661 +          // If a save command is defined, call it.
  1.3662 +          CodeMirror.commands.save(cm);
  1.3663 +        } else {
  1.3664 +          // Saves to text area if no save command is defined.
  1.3665 +          cm.save();
  1.3666 +        }
  1.3667 +      },
  1.3668 +      nohlsearch: function(cm) {
  1.3669 +        clearSearchHighlight(cm);
  1.3670 +      },
  1.3671 +      delmarks: function(cm, params) {
  1.3672 +        if (!params.argString || !trim(params.argString)) {
  1.3673 +          showConfirm(cm, 'Argument required');
  1.3674 +          return;
  1.3675 +        }
  1.3676 +
  1.3677 +        var state = cm.state.vim;
  1.3678 +        var stream = new CodeMirror.StringStream(trim(params.argString));
  1.3679 +        while (!stream.eol()) {
  1.3680 +          stream.eatSpace();
  1.3681 +
  1.3682 +          // Record the streams position at the beginning of the loop for use
  1.3683 +          // in error messages.
  1.3684 +          var count = stream.pos;
  1.3685 +
  1.3686 +          if (!stream.match(/[a-zA-Z]/, false)) {
  1.3687 +            showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
  1.3688 +            return;
  1.3689 +          }
  1.3690 +
  1.3691 +          var sym = stream.next();
  1.3692 +          // Check if this symbol is part of a range
  1.3693 +          if (stream.match('-', true)) {
  1.3694 +            // This symbol is part of a range.
  1.3695 +
  1.3696 +            // The range must terminate at an alphabetic character.
  1.3697 +            if (!stream.match(/[a-zA-Z]/, false)) {
  1.3698 +              showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
  1.3699 +              return;
  1.3700 +            }
  1.3701 +
  1.3702 +            var startMark = sym;
  1.3703 +            var finishMark = stream.next();
  1.3704 +            // The range must terminate at an alphabetic character which
  1.3705 +            // shares the same case as the start of the range.
  1.3706 +            if (isLowerCase(startMark) && isLowerCase(finishMark) ||
  1.3707 +                isUpperCase(startMark) && isUpperCase(finishMark)) {
  1.3708 +              var start = startMark.charCodeAt(0);
  1.3709 +              var finish = finishMark.charCodeAt(0);
  1.3710 +              if (start >= finish) {
  1.3711 +                showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
  1.3712 +                return;
  1.3713 +              }
  1.3714 +
  1.3715 +              // Because marks are always ASCII values, and we have
  1.3716 +              // determined that they are the same case, we can use
  1.3717 +              // their char codes to iterate through the defined range.
  1.3718 +              for (var j = 0; j <= finish - start; j++) {
  1.3719 +                var mark = String.fromCharCode(start + j);
  1.3720 +                delete state.marks[mark];
  1.3721 +              }
  1.3722 +            } else {
  1.3723 +              showConfirm(cm, 'Invalid argument: ' + startMark + '-');
  1.3724 +              return;
  1.3725 +            }
  1.3726 +          } else {
  1.3727 +            // This symbol is a valid mark, and is not part of a range.
  1.3728 +            delete state.marks[sym];
  1.3729 +          }
  1.3730 +        }
  1.3731 +      }
  1.3732 +    };
  1.3733 +
  1.3734 +    var exCommandDispatcher = new Vim.ExCommandDispatcher();
  1.3735 +
  1.3736 +    /**
  1.3737 +    * @param {CodeMirror} cm CodeMirror instance we are in.
  1.3738 +    * @param {boolean} confirm Whether to confirm each replace.
  1.3739 +    * @param {Cursor} lineStart Line to start replacing from.
  1.3740 +    * @param {Cursor} lineEnd Line to stop replacing at.
  1.3741 +    * @param {RegExp} query Query for performing matches with.
  1.3742 +    * @param {string} replaceWith Text to replace matches with. May contain $1,
  1.3743 +    *     $2, etc for replacing captured groups using Javascript replace.
  1.3744 +    */
  1.3745 +    function doReplace(cm, confirm, lineStart, lineEnd, searchCursor, query,
  1.3746 +        replaceWith) {
  1.3747 +      // Set up all the functions.
  1.3748 +      cm.state.vim.exMode = true;
  1.3749 +      var done = false;
  1.3750 +      var lastPos = searchCursor.from();
  1.3751 +      function replaceAll() {
  1.3752 +        cm.operation(function() {
  1.3753 +          while (!done) {
  1.3754 +            replace();
  1.3755 +            next();
  1.3756 +          }
  1.3757 +          stop();
  1.3758 +        });
  1.3759 +      }
  1.3760 +      function replace() {
  1.3761 +        var text = cm.getRange(searchCursor.from(), searchCursor.to());
  1.3762 +        var newText = text.replace(query, replaceWith);
  1.3763 +        searchCursor.replace(newText);
  1.3764 +      }
  1.3765 +      function next() {
  1.3766 +        var found = searchCursor.findNext();
  1.3767 +        if (!found) {
  1.3768 +          done = true;
  1.3769 +        } else if (isInRange(searchCursor.from(), lineStart, lineEnd)) {
  1.3770 +          cm.scrollIntoView(searchCursor.from(), 30);
  1.3771 +          cm.setSelection(searchCursor.from(), searchCursor.to());
  1.3772 +          lastPos = searchCursor.from();
  1.3773 +          done = false;
  1.3774 +        } else {
  1.3775 +          done = true;
  1.3776 +        }
  1.3777 +      }
  1.3778 +      function stop(close) {
  1.3779 +        if (close) { close(); }
  1.3780 +        cm.focus();
  1.3781 +        if (lastPos) {
  1.3782 +          cm.setCursor(lastPos);
  1.3783 +          var vim = cm.state.vim;
  1.3784 +          vim.exMode = false;
  1.3785 +          vim.lastHPos = vim.lastHSPos = lastPos.ch;
  1.3786 +        }
  1.3787 +      }
  1.3788 +      function onPromptKeyDown(e, _value, close) {
  1.3789 +        // Swallow all keys.
  1.3790 +        CodeMirror.e_stop(e);
  1.3791 +        var keyName = CodeMirror.keyName(e);
  1.3792 +        switch (keyName) {
  1.3793 +          case 'Y':
  1.3794 +            replace(); next(); break;
  1.3795 +          case 'N':
  1.3796 +            next(); break;
  1.3797 +          case 'A':
  1.3798 +            cm.operation(replaceAll); break;
  1.3799 +          case 'L':
  1.3800 +            replace();
  1.3801 +            // fall through and exit.
  1.3802 +          case 'Q':
  1.3803 +          case 'Esc':
  1.3804 +          case 'Ctrl-C':
  1.3805 +          case 'Ctrl-[':
  1.3806 +            stop(close);
  1.3807 +            break;
  1.3808 +        }
  1.3809 +        if (done) { stop(close); }
  1.3810 +      }
  1.3811 +
  1.3812 +      // Actually do replace.
  1.3813 +      next();
  1.3814 +      if (done) {
  1.3815 +        showConfirm(cm, 'No matches for ' + query.source);
  1.3816 +        return;
  1.3817 +      }
  1.3818 +      if (!confirm) {
  1.3819 +        replaceAll();
  1.3820 +        return;
  1.3821 +      }
  1.3822 +      showPrompt(cm, {
  1.3823 +        prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)',
  1.3824 +        onKeyDown: onPromptKeyDown
  1.3825 +      });
  1.3826 +    }
  1.3827 +
  1.3828 +    // Register Vim with CodeMirror
  1.3829 +    function buildVimKeyMap() {
  1.3830 +      /**
  1.3831 +       * Handle the raw key event from CodeMirror. Translate the
  1.3832 +       * Shift + key modifier to the resulting letter, while preserving other
  1.3833 +       * modifers.
  1.3834 +       */
  1.3835 +      function cmKeyToVimKey(key, modifier) {
  1.3836 +        var vimKey = key;
  1.3837 +        if (isUpperCase(vimKey) && modifier == 'Ctrl') {
  1.3838 +            vimKey = vimKey.toLowerCase();
  1.3839 +        }
  1.3840 +        if (modifier) {
  1.3841 +          // Vim will parse modifier+key combination as a single key.
  1.3842 +          vimKey = modifier.charAt(0) + '-' + vimKey;
  1.3843 +        }
  1.3844 +        var specialKey = ({Enter:'CR',Backspace:'BS',Delete:'Del'})[vimKey];
  1.3845 +        vimKey = specialKey ? specialKey : vimKey;
  1.3846 +        vimKey = vimKey.length > 1 ? '<'+ vimKey + '>' : vimKey;
  1.3847 +        return vimKey;
  1.3848 +      }
  1.3849 +
  1.3850 +      // Closure to bind CodeMirror, key, modifier.
  1.3851 +      function keyMapper(vimKey) {
  1.3852 +        return function(cm) {
  1.3853 +          CodeMirror.Vim.handleKey(cm, vimKey);
  1.3854 +        };
  1.3855 +      }
  1.3856 +
  1.3857 +      var cmToVimKeymap = {
  1.3858 +        'nofallthrough': true,
  1.3859 +        'style': 'fat-cursor'
  1.3860 +      };
  1.3861 +      function bindKeys(keys, modifier) {
  1.3862 +        for (var i = 0; i < keys.length; i++) {
  1.3863 +          var key = keys[i];
  1.3864 +          if (!modifier && key.length == 1) {
  1.3865 +            // Wrap all keys without modifiers with '' to identify them by their
  1.3866 +            // key characters instead of key identifiers.
  1.3867 +            key = "'" + key + "'";
  1.3868 +          }
  1.3869 +          var vimKey = cmKeyToVimKey(keys[i], modifier);
  1.3870 +          var cmKey = modifier ? modifier + '-' + key : key;
  1.3871 +          cmToVimKeymap[cmKey] = keyMapper(vimKey);
  1.3872 +        }
  1.3873 +      }
  1.3874 +      bindKeys(upperCaseAlphabet);
  1.3875 +      bindKeys(lowerCaseAlphabet);
  1.3876 +      bindKeys(upperCaseAlphabet, 'Ctrl');
  1.3877 +      bindKeys(specialSymbols);
  1.3878 +      bindKeys(specialSymbols, 'Ctrl');
  1.3879 +      bindKeys(numbers);
  1.3880 +      bindKeys(numbers, 'Ctrl');
  1.3881 +      bindKeys(specialKeys);
  1.3882 +      bindKeys(specialKeys, 'Ctrl');
  1.3883 +      return cmToVimKeymap;
  1.3884 +    }
  1.3885 +    CodeMirror.keyMap.vim = buildVimKeyMap();
  1.3886 +
  1.3887 +    function exitInsertMode(cm) {
  1.3888 +      var vim = cm.state.vim;
  1.3889 +      var macroModeState = vimGlobalState.macroModeState;
  1.3890 +      var isPlaying = macroModeState.isPlaying;
  1.3891 +      if (!isPlaying) {
  1.3892 +        cm.off('change', onChange);
  1.3893 +        cm.off('cursorActivity', onCursorActivity);
  1.3894 +        CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
  1.3895 +      }
  1.3896 +      if (!isPlaying && vim.insertModeRepeat > 1) {
  1.3897 +        // Perform insert mode repeat for commands like 3,a and 3,o.
  1.3898 +        repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
  1.3899 +            true /** repeatForInsert */);
  1.3900 +        vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;
  1.3901 +      }
  1.3902 +      delete vim.insertModeRepeat;
  1.3903 +      cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1);
  1.3904 +      vim.insertMode = false;
  1.3905 +      cm.setOption('keyMap', 'vim');
  1.3906 +      cm.setOption('disableInput', true);
  1.3907 +      cm.toggleOverwrite(false); // exit replace mode if we were in it.
  1.3908 +      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
  1.3909 +      if (macroModeState.isRecording) {
  1.3910 +        logInsertModeChange(macroModeState);
  1.3911 +      }
  1.3912 +    }
  1.3913 +
  1.3914 +    CodeMirror.keyMap['vim-insert'] = {
  1.3915 +      // TODO: override navigation keys so that Esc will cancel automatic
  1.3916 +      // indentation from o, O, i_<CR>
  1.3917 +      'Esc': exitInsertMode,
  1.3918 +      'Ctrl-[': exitInsertMode,
  1.3919 +      'Ctrl-C': exitInsertMode,
  1.3920 +      'Ctrl-N': 'autocomplete',
  1.3921 +      'Ctrl-P': 'autocomplete',
  1.3922 +      'Enter': function(cm) {
  1.3923 +        var fn = CodeMirror.commands.newlineAndIndentContinueComment ||
  1.3924 +            CodeMirror.commands.newlineAndIndent;
  1.3925 +        fn(cm);
  1.3926 +      },
  1.3927 +      fallthrough: ['default']
  1.3928 +    };
  1.3929 +
  1.3930 +    CodeMirror.keyMap['vim-replace'] = {
  1.3931 +      'Backspace': 'goCharLeft',
  1.3932 +      fallthrough: ['vim-insert']
  1.3933 +    };
  1.3934 +
  1.3935 +    function executeMacroRegister(cm, vim, macroModeState, registerName) {
  1.3936 +      var register = vimGlobalState.registerController.getRegister(registerName);
  1.3937 +      var keyBuffer = register.keyBuffer;
  1.3938 +      var imc = 0;
  1.3939 +      macroModeState.isPlaying = true;
  1.3940 +      for (var i = 0; i < keyBuffer.length; i++) {
  1.3941 +        var text = keyBuffer[i];
  1.3942 +        var match, key;
  1.3943 +        while (text) {
  1.3944 +          // Pull off one command key, which is either a single character
  1.3945 +          // or a special sequence wrapped in '<' and '>', e.g. '<Space>'.
  1.3946 +          match = (/<\w+-.+?>|<\w+>|./).exec(text);
  1.3947 +          key = match[0];
  1.3948 +          text = text.substring(match.index + key.length);
  1.3949 +          CodeMirror.Vim.handleKey(cm, key);
  1.3950 +          if (vim.insertMode) {
  1.3951 +            repeatInsertModeChanges(
  1.3952 +                cm, register.insertModeChanges[imc++].changes, 1);
  1.3953 +            exitInsertMode(cm);
  1.3954 +          }
  1.3955 +        }
  1.3956 +      };
  1.3957 +      macroModeState.isPlaying = false;
  1.3958 +    }
  1.3959 +
  1.3960 +    function logKey(macroModeState, key) {
  1.3961 +      if (macroModeState.isPlaying) { return; }
  1.3962 +      var registerName = macroModeState.latestRegister;
  1.3963 +      var register = vimGlobalState.registerController.getRegister(registerName);
  1.3964 +      if (register) {
  1.3965 +        register.pushText(key);
  1.3966 +      }
  1.3967 +    }
  1.3968 +
  1.3969 +    function logInsertModeChange(macroModeState) {
  1.3970 +      if (macroModeState.isPlaying) { return; }
  1.3971 +      var registerName = macroModeState.latestRegister;
  1.3972 +      var register = vimGlobalState.registerController.getRegister(registerName);
  1.3973 +      if (register) {
  1.3974 +        register.pushInsertModeChanges(macroModeState.lastInsertModeChanges);
  1.3975 +      }
  1.3976 +    }
  1.3977 +
  1.3978 +    /**
  1.3979 +     * Listens for changes made in insert mode.
  1.3980 +     * Should only be active in insert mode.
  1.3981 +     */
  1.3982 +    function onChange(_cm, changeObj) {
  1.3983 +      var macroModeState = vimGlobalState.macroModeState;
  1.3984 +      var lastChange = macroModeState.lastInsertModeChanges;
  1.3985 +      if (!macroModeState.isPlaying) {
  1.3986 +        while(changeObj) {
  1.3987 +          lastChange.expectCursorActivityForChange = true;
  1.3988 +          if (changeObj.origin == '+input' || changeObj.origin == 'paste'
  1.3989 +              || changeObj.origin === undefined /* only in testing */) {
  1.3990 +            var text = changeObj.text.join('\n');
  1.3991 +            lastChange.changes.push(text);
  1.3992 +          }
  1.3993 +          // Change objects may be chained with next.
  1.3994 +          changeObj = changeObj.next;
  1.3995 +        }
  1.3996 +      }
  1.3997 +    }
  1.3998 +
  1.3999 +    /**
  1.4000 +    * Listens for any kind of cursor activity on CodeMirror.
  1.4001 +    * - For tracking cursor activity in insert mode.
  1.4002 +    * - Should only be active in insert mode.
  1.4003 +    */
  1.4004 +    function onCursorActivity() {
  1.4005 +      var macroModeState = vimGlobalState.macroModeState;
  1.4006 +      if (macroModeState.isPlaying) { return; }
  1.4007 +      var lastChange = macroModeState.lastInsertModeChanges;
  1.4008 +      if (lastChange.expectCursorActivityForChange) {
  1.4009 +        lastChange.expectCursorActivityForChange = false;
  1.4010 +      } else {
  1.4011 +        // Cursor moved outside the context of an edit. Reset the change.
  1.4012 +        lastChange.changes = [];
  1.4013 +      }
  1.4014 +    }
  1.4015 +
  1.4016 +    /** Wrapper for special keys pressed in insert mode */
  1.4017 +    function InsertModeKey(keyName) {
  1.4018 +      this.keyName = keyName;
  1.4019 +    }
  1.4020 +
  1.4021 +    /**
  1.4022 +    * Handles raw key down events from the text area.
  1.4023 +    * - Should only be active in insert mode.
  1.4024 +    * - For recording deletes in insert mode.
  1.4025 +    */
  1.4026 +    function onKeyEventTargetKeyDown(e) {
  1.4027 +      var macroModeState = vimGlobalState.macroModeState;
  1.4028 +      var lastChange = macroModeState.lastInsertModeChanges;
  1.4029 +      var keyName = CodeMirror.keyName(e);
  1.4030 +      function onKeyFound() {
  1.4031 +        lastChange.changes.push(new InsertModeKey(keyName));
  1.4032 +        return true;
  1.4033 +      }
  1.4034 +      if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
  1.4035 +        CodeMirror.lookupKey(keyName, ['vim-insert'], onKeyFound);
  1.4036 +      }
  1.4037 +    }
  1.4038 +
  1.4039 +    /**
  1.4040 +     * Repeats the last edit, which includes exactly 1 command and at most 1
  1.4041 +     * insert. Operator and motion commands are read from lastEditInputState,
  1.4042 +     * while action commands are read from lastEditActionCommand.
  1.4043 +     *
  1.4044 +     * If repeatForInsert is true, then the function was called by
  1.4045 +     * exitInsertMode to repeat the insert mode changes the user just made. The
  1.4046 +     * corresponding enterInsertMode call was made with a count.
  1.4047 +     */
  1.4048 +    function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
  1.4049 +      var macroModeState = vimGlobalState.macroModeState;
  1.4050 +      macroModeState.isPlaying = true;
  1.4051 +      var isAction = !!vim.lastEditActionCommand;
  1.4052 +      var cachedInputState = vim.inputState;
  1.4053 +      function repeatCommand() {
  1.4054 +        if (isAction) {
  1.4055 +          commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);
  1.4056 +        } else {
  1.4057 +          commandDispatcher.evalInput(cm, vim);
  1.4058 +        }
  1.4059 +      }
  1.4060 +      function repeatInsert(repeat) {
  1.4061 +        if (macroModeState.lastInsertModeChanges.changes.length > 0) {
  1.4062 +          // For some reason, repeat cw in desktop VIM does not repeat
  1.4063 +          // insert mode changes. Will conform to that behavior.
  1.4064 +          repeat = !vim.lastEditActionCommand ? 1 : repeat;
  1.4065 +          var changeObject = macroModeState.lastInsertModeChanges;
  1.4066 +          // This isn't strictly necessary, but since lastInsertModeChanges is
  1.4067 +          // supposed to be immutable during replay, this helps catch bugs.
  1.4068 +          macroModeState.lastInsertModeChanges = {};
  1.4069 +          repeatInsertModeChanges(cm, changeObject.changes, repeat);
  1.4070 +          macroModeState.lastInsertModeChanges = changeObject;
  1.4071 +        }
  1.4072 +      }
  1.4073 +      vim.inputState = vim.lastEditInputState;
  1.4074 +      if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {
  1.4075 +        // o and O repeat have to be interlaced with insert repeats so that the
  1.4076 +        // insertions appear on separate lines instead of the last line.
  1.4077 +        for (var i = 0; i < repeat; i++) {
  1.4078 +          repeatCommand();
  1.4079 +          repeatInsert(1);
  1.4080 +        }
  1.4081 +      } else {
  1.4082 +        if (!repeatForInsert) {
  1.4083 +          // Hack to get the cursor to end up at the right place. If I is
  1.4084 +          // repeated in insert mode repeat, cursor will be 1 insert
  1.4085 +          // change set left of where it should be.
  1.4086 +          repeatCommand();
  1.4087 +        }
  1.4088 +        repeatInsert(repeat);
  1.4089 +      }
  1.4090 +      vim.inputState = cachedInputState;
  1.4091 +      if (vim.insertMode && !repeatForInsert) {
  1.4092 +        // Don't exit insert mode twice. If repeatForInsert is set, then we
  1.4093 +        // were called by an exitInsertMode call lower on the stack.
  1.4094 +        exitInsertMode(cm);
  1.4095 +      }
  1.4096 +      macroModeState.isPlaying = false;
  1.4097 +    };
  1.4098 +
  1.4099 +    function repeatInsertModeChanges(cm, changes, repeat) {
  1.4100 +      function keyHandler(binding) {
  1.4101 +        if (typeof binding == 'string') {
  1.4102 +          CodeMirror.commands[binding](cm);
  1.4103 +        } else {
  1.4104 +          binding(cm);
  1.4105 +        }
  1.4106 +        return true;
  1.4107 +      }
  1.4108 +      for (var i = 0; i < repeat; i++) {
  1.4109 +        for (var j = 0; j < changes.length; j++) {
  1.4110 +          var change = changes[j];
  1.4111 +          if (change instanceof InsertModeKey) {
  1.4112 +            CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler);
  1.4113 +          } else {
  1.4114 +            var cur = cm.getCursor();
  1.4115 +            cm.replaceRange(change, cur, cur);
  1.4116 +          }
  1.4117 +        }
  1.4118 +      }
  1.4119 +    }
  1.4120 +
  1.4121 +    resetVimGlobalState();
  1.4122 +    return vimApi;
  1.4123 +  };
  1.4124 +  // Initialize Vim and make it available as an API.
  1.4125 +  CodeMirror.Vim = Vim();
  1.4126 +});

mercurial