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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /**
     2  * Supported keybindings:
     3  *
     4  *   Motion:
     5  *   h, j, k, l
     6  *   gj, gk
     7  *   e, E, w, W, b, B, ge, gE
     8  *   f<character>, F<character>, t<character>, T<character>
     9  *   $, ^, 0, -, +, _
    10  *   gg, G
    11  *   %
    12  *   '<character>, `<character>
    13  *
    14  *   Operator:
    15  *   d, y, c
    16  *   dd, yy, cc
    17  *   g~, g~g~
    18  *   >, <, >>, <<
    19  *
    20  *   Operator-Motion:
    21  *   x, X, D, Y, C, ~
    22  *
    23  *   Action:
    24  *   a, i, s, A, I, S, o, O
    25  *   zz, z., z<CR>, zt, zb, z-
    26  *   J
    27  *   u, Ctrl-r
    28  *   m<character>
    29  *   r<character>
    30  *
    31  *   Modes:
    32  *   ESC - leave insert mode, visual mode, and clear input state.
    33  *   Ctrl-[, Ctrl-c - same as ESC.
    34  *
    35  * Registers: unnamed, -, a-z, A-Z, 0-9
    36  *   (Does not respect the special case for number registers when delete
    37  *    operator is made with these commands: %, (, ),  , /, ?, n, N, {, } )
    38  *   TODO: Implement the remaining registers.
    39  * Marks: a-z, A-Z, and 0-9
    40  *   TODO: Implement the remaining special marks. They have more complex
    41  *       behavior.
    42  *
    43  * Events:
    44  *  'vim-mode-change' - raised on the editor anytime the current mode changes,
    45  *                      Event object: {mode: "visual", subMode: "linewise"}
    46  *
    47  * Code structure:
    48  *  1. Default keymap
    49  *  2. Variable declarations and short basic helpers
    50  *  3. Instance (External API) implementation
    51  *  4. Internal state tracking objects (input state, counter) implementation
    52  *     and instanstiation
    53  *  5. Key handler (the main command dispatcher) implementation
    54  *  6. Motion, operator, and action implementations
    55  *  7. Helper functions for the key handler, motions, operators, and actions
    56  *  8. Set up Vim to work as a keymap for CodeMirror.
    57  */
    59 (function(mod) {
    60   if (typeof exports == "object" && typeof module == "object") // CommonJS
    61     mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog"));
    62   else if (typeof define == "function" && define.amd) // AMD
    63     define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog"], mod);
    64   else // Plain browser env
    65     mod(CodeMirror);
    66 })(function(CodeMirror) {
    67   'use strict';
    69   var defaultKeymap = [
    70     // Key to key mapping. This goes first to make it possible to override
    71     // existing mappings.
    72     { keys: ['<Left>'], type: 'keyToKey', toKeys: ['h'] },
    73     { keys: ['<Right>'], type: 'keyToKey', toKeys: ['l'] },
    74     { keys: ['<Up>'], type: 'keyToKey', toKeys: ['k'] },
    75     { keys: ['<Down>'], type: 'keyToKey', toKeys: ['j'] },
    76     { keys: ['<Space>'], type: 'keyToKey', toKeys: ['l'] },
    77     { keys: ['<BS>'], type: 'keyToKey', toKeys: ['h'] },
    78     { keys: ['<C-Space>'], type: 'keyToKey', toKeys: ['W'] },
    79     { keys: ['<C-BS>'], type: 'keyToKey', toKeys: ['B'] },
    80     { keys: ['<S-Space>'], type: 'keyToKey', toKeys: ['w'] },
    81     { keys: ['<S-BS>'], type: 'keyToKey', toKeys: ['b'] },
    82     { keys: ['<C-n>'], type: 'keyToKey', toKeys: ['j'] },
    83     { keys: ['<C-p>'], type: 'keyToKey', toKeys: ['k'] },
    84     { keys: ['<C-[>'], type: 'keyToKey', toKeys: ['<Esc>'] },
    85     { keys: ['<C-c>'], type: 'keyToKey', toKeys: ['<Esc>'] },
    86     { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'], context: 'normal' },
    87     { keys: ['s'], type: 'keyToKey', toKeys: ['x', 'i'], context: 'visual'},
    88     { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'], context: 'normal' },
    89     { keys: ['S'], type: 'keyToKey', toKeys: ['d', 'c', 'c'], context: 'visual' },
    90     { keys: ['<Home>'], type: 'keyToKey', toKeys: ['0'] },
    91     { keys: ['<End>'], type: 'keyToKey', toKeys: ['$'] },
    92     { keys: ['<PageUp>'], type: 'keyToKey', toKeys: ['<C-b>'] },
    93     { keys: ['<PageDown>'], type: 'keyToKey', toKeys: ['<C-f>'] },
    94     { keys: ['<CR>'], type: 'keyToKey', toKeys: ['j', '^'], context: 'normal' },
    95     // Motions
    96     { keys: ['H'], type: 'motion',
    97         motion: 'moveToTopLine',
    98         motionArgs: { linewise: true, toJumplist: true }},
    99     { keys: ['M'], type: 'motion',
   100         motion: 'moveToMiddleLine',
   101         motionArgs: { linewise: true, toJumplist: true }},
   102     { keys: ['L'], type: 'motion',
   103         motion: 'moveToBottomLine',
   104         motionArgs: { linewise: true, toJumplist: true }},
   105     { keys: ['h'], type: 'motion',
   106         motion: 'moveByCharacters',
   107         motionArgs: { forward: false }},
   108     { keys: ['l'], type: 'motion',
   109         motion: 'moveByCharacters',
   110         motionArgs: { forward: true }},
   111     { keys: ['j'], type: 'motion',
   112         motion: 'moveByLines',
   113         motionArgs: { forward: true, linewise: true }},
   114     { keys: ['k'], type: 'motion',
   115         motion: 'moveByLines',
   116         motionArgs: { forward: false, linewise: true }},
   117     { keys: ['g','j'], type: 'motion',
   118         motion: 'moveByDisplayLines',
   119         motionArgs: { forward: true }},
   120     { keys: ['g','k'], type: 'motion',
   121         motion: 'moveByDisplayLines',
   122         motionArgs: { forward: false }},
   123     { keys: ['w'], type: 'motion',
   124         motion: 'moveByWords',
   125         motionArgs: { forward: true, wordEnd: false }},
   126     { keys: ['W'], type: 'motion',
   127         motion: 'moveByWords',
   128         motionArgs: { forward: true, wordEnd: false, bigWord: true }},
   129     { keys: ['e'], type: 'motion',
   130         motion: 'moveByWords',
   131         motionArgs: { forward: true, wordEnd: true, inclusive: true }},
   132     { keys: ['E'], type: 'motion',
   133         motion: 'moveByWords',
   134         motionArgs: { forward: true, wordEnd: true, bigWord: true,
   135             inclusive: true }},
   136     { keys: ['b'], type: 'motion',
   137         motion: 'moveByWords',
   138         motionArgs: { forward: false, wordEnd: false }},
   139     { keys: ['B'], type: 'motion',
   140         motion: 'moveByWords',
   141         motionArgs: { forward: false, wordEnd: false, bigWord: true }},
   142     { keys: ['g', 'e'], type: 'motion',
   143         motion: 'moveByWords',
   144         motionArgs: { forward: false, wordEnd: true, inclusive: true }},
   145     { keys: ['g', 'E'], type: 'motion',
   146         motion: 'moveByWords',
   147         motionArgs: { forward: false, wordEnd: true, bigWord: true,
   148             inclusive: true }},
   149     { keys: ['{'], type: 'motion', motion: 'moveByParagraph',
   150         motionArgs: { forward: false, toJumplist: true }},
   151     { keys: ['}'], type: 'motion', motion: 'moveByParagraph',
   152         motionArgs: { forward: true, toJumplist: true }},
   153     { keys: ['<C-f>'], type: 'motion',
   154         motion: 'moveByPage', motionArgs: { forward: true }},
   155     { keys: ['<C-b>'], type: 'motion',
   156         motion: 'moveByPage', motionArgs: { forward: false }},
   157     { keys: ['<C-d>'], type: 'motion',
   158         motion: 'moveByScroll',
   159         motionArgs: { forward: true, explicitRepeat: true }},
   160     { keys: ['<C-u>'], type: 'motion',
   161         motion: 'moveByScroll',
   162         motionArgs: { forward: false, explicitRepeat: true }},
   163     { keys: ['g', 'g'], type: 'motion',
   164         motion: 'moveToLineOrEdgeOfDocument',
   165         motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
   166     { keys: ['G'], type: 'motion',
   167         motion: 'moveToLineOrEdgeOfDocument',
   168         motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
   169     { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' },
   170     { keys: ['^'], type: 'motion',
   171         motion: 'moveToFirstNonWhiteSpaceCharacter' },
   172     { keys: ['+'], type: 'motion',
   173         motion: 'moveByLines',
   174         motionArgs: { forward: true, toFirstChar:true }},
   175     { keys: ['-'], type: 'motion',
   176         motion: 'moveByLines',
   177         motionArgs: { forward: false, toFirstChar:true }},
   178     { keys: ['_'], type: 'motion',
   179         motion: 'moveByLines',
   180         motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
   181     { keys: ['$'], type: 'motion',
   182         motion: 'moveToEol',
   183         motionArgs: { inclusive: true }},
   184     { keys: ['%'], type: 'motion',
   185         motion: 'moveToMatchedSymbol',
   186         motionArgs: { inclusive: true, toJumplist: true }},
   187     { keys: ['f', 'character'], type: 'motion',
   188         motion: 'moveToCharacter',
   189         motionArgs: { forward: true , inclusive: true }},
   190     { keys: ['F', 'character'], type: 'motion',
   191         motion: 'moveToCharacter',
   192         motionArgs: { forward: false }},
   193     { keys: ['t', 'character'], type: 'motion',
   194         motion: 'moveTillCharacter',
   195         motionArgs: { forward: true, inclusive: true }},
   196     { keys: ['T', 'character'], type: 'motion',
   197         motion: 'moveTillCharacter',
   198         motionArgs: { forward: false }},
   199     { keys: [';'], type: 'motion', motion: 'repeatLastCharacterSearch',
   200         motionArgs: { forward: true }},
   201     { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch',
   202         motionArgs: { forward: false }},
   203     { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark',
   204         motionArgs: {toJumplist: true}},
   205     { keys: ['`', 'character'], type: 'motion', motion: 'goToMark',
   206         motionArgs: {toJumplist: true}},
   207     { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
   208     { keys: ['[', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
   209     { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
   210     { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
   211     { keys: [']', 'character'], type: 'motion',
   212         motion: 'moveToSymbol',
   213         motionArgs: { forward: true, toJumplist: true}},
   214     { keys: ['[', 'character'], type: 'motion',
   215         motion: 'moveToSymbol',
   216         motionArgs: { forward: false, toJumplist: true}},
   217     { keys: ['|'], type: 'motion',
   218         motion: 'moveToColumn',
   219         motionArgs: { }},
   220     { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { },context:'visual'},
   221     // Operators
   222     { keys: ['d'], type: 'operator', operator: 'delete' },
   223     { keys: ['y'], type: 'operator', operator: 'yank' },
   224     { keys: ['c'], type: 'operator', operator: 'change' },
   225     { keys: ['>'], type: 'operator', operator: 'indent',
   226         operatorArgs: { indentRight: true }},
   227     { keys: ['<'], type: 'operator', operator: 'indent',
   228         operatorArgs: { indentRight: false }},
   229     { keys: ['g', '~'], type: 'operator', operator: 'swapcase' },
   230     { keys: ['n'], type: 'motion', motion: 'findNext',
   231         motionArgs: { forward: true, toJumplist: true }},
   232     { keys: ['N'], type: 'motion', motion: 'findNext',
   233         motionArgs: { forward: false, toJumplist: true }},
   234     // Operator-Motion dual commands
   235     { keys: ['x'], type: 'operatorMotion', operator: 'delete',
   236         motion: 'moveByCharacters', motionArgs: { forward: true },
   237         operatorMotionArgs: { visualLine: false }},
   238     { keys: ['X'], type: 'operatorMotion', operator: 'delete',
   239         motion: 'moveByCharacters', motionArgs: { forward: false },
   240         operatorMotionArgs: { visualLine: true }},
   241     { keys: ['D'], type: 'operatorMotion', operator: 'delete',
   242       motion: 'moveToEol', motionArgs: { inclusive: true },
   243         operatorMotionArgs: { visualLine: true }},
   244     { keys: ['Y'], type: 'operatorMotion', operator: 'yank',
   245         motion: 'moveToEol', motionArgs: { inclusive: true },
   246         operatorMotionArgs: { visualLine: true }},
   247     { keys: ['C'], type: 'operatorMotion',
   248         operator: 'change',
   249         motion: 'moveToEol', motionArgs: { inclusive: true },
   250         operatorMotionArgs: { visualLine: true }},
   251     { keys: ['~'], type: 'operatorMotion',
   252         operator: 'swapcase', operatorArgs: { shouldMoveCursor: true },
   253         motion: 'moveByCharacters', motionArgs: { forward: true }},
   254     // Actions
   255     { keys: ['<C-i>'], type: 'action', action: 'jumpListWalk',
   256         actionArgs: { forward: true }},
   257     { keys: ['<C-o>'], type: 'action', action: 'jumpListWalk',
   258         actionArgs: { forward: false }},
   259     { keys: ['<C-e>'], type: 'action',
   260         action: 'scroll',
   261         actionArgs: { forward: true, linewise: true }},
   262     { keys: ['<C-y>'], type: 'action',
   263         action: 'scroll',
   264         actionArgs: { forward: false, linewise: true }},
   265     { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true,
   266         actionArgs: { insertAt: 'charAfter' }},
   267     { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true,
   268         actionArgs: { insertAt: 'eol' }},
   269     { keys: ['i'], type: 'action', action: 'enterInsertMode', isEdit: true,
   270         actionArgs: { insertAt: 'inplace' }},
   271     { keys: ['I'], type: 'action', action: 'enterInsertMode', isEdit: true,
   272         actionArgs: { insertAt: 'firstNonBlank' }},
   273     { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode',
   274         isEdit: true, interlaceInsertRepeat: true,
   275         actionArgs: { after: true }},
   276     { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode',
   277         isEdit: true, interlaceInsertRepeat: true,
   278         actionArgs: { after: false }},
   279     { keys: ['v'], type: 'action', action: 'toggleVisualMode' },
   280     { keys: ['V'], type: 'action', action: 'toggleVisualMode',
   281         actionArgs: { linewise: true }},
   282     { keys: ['g', 'v'], type: 'action', action: 'reselectLastSelection' },
   283     { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true },
   284     { keys: ['p'], type: 'action', action: 'paste', isEdit: true,
   285         actionArgs: { after: true, isEdit: true }},
   286     { keys: ['P'], type: 'action', action: 'paste', isEdit: true,
   287         actionArgs: { after: false, isEdit: true }},
   288     { keys: ['r', 'character'], type: 'action', action: 'replace', isEdit: true },
   289     { keys: ['@', 'character'], type: 'action', action: 'replayMacro' },
   290     { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' },
   291     // Handle Replace-mode as a special case of insert mode.
   292     { keys: ['R'], type: 'action', action: 'enterInsertMode', isEdit: true,
   293         actionArgs: { replace: true }},
   294     { keys: ['u'], type: 'action', action: 'undo' },
   295     { keys: ['<C-r>'], type: 'action', action: 'redo' },
   296     { keys: ['m', 'character'], type: 'action', action: 'setMark' },
   297     { keys: ['"', 'character'], type: 'action', action: 'setRegister' },
   298     { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor',
   299         actionArgs: { position: 'center' }},
   300     { keys: ['z', '.'], type: 'action', action: 'scrollToCursor',
   301         actionArgs: { position: 'center' },
   302         motion: 'moveToFirstNonWhiteSpaceCharacter' },
   303     { keys: ['z', 't'], type: 'action', action: 'scrollToCursor',
   304         actionArgs: { position: 'top' }},
   305     { keys: ['z', '<CR>'], type: 'action', action: 'scrollToCursor',
   306         actionArgs: { position: 'top' },
   307         motion: 'moveToFirstNonWhiteSpaceCharacter' },
   308     { keys: ['z', '-'], type: 'action', action: 'scrollToCursor',
   309         actionArgs: { position: 'bottom' }},
   310     { keys: ['z', 'b'], type: 'action', action: 'scrollToCursor',
   311         actionArgs: { position: 'bottom' },
   312         motion: 'moveToFirstNonWhiteSpaceCharacter' },
   313     { keys: ['.'], type: 'action', action: 'repeatLastEdit' },
   314     { keys: ['<C-a>'], type: 'action', action: 'incrementNumberToken',
   315         isEdit: true,
   316         actionArgs: {increase: true, backtrack: false}},
   317     { keys: ['<C-x>'], type: 'action', action: 'incrementNumberToken',
   318         isEdit: true,
   319         actionArgs: {increase: false, backtrack: false}},
   320     // Text object motions
   321     { keys: ['a', 'character'], type: 'motion',
   322         motion: 'textObjectManipulation' },
   323     { keys: ['i', 'character'], type: 'motion',
   324         motion: 'textObjectManipulation',
   325         motionArgs: { textObjectInner: true }},
   326     // Search
   327     { keys: ['/'], type: 'search',
   328         searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
   329     { keys: ['?'], type: 'search',
   330         searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
   331     { keys: ['*'], type: 'search',
   332         searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
   333     { keys: ['#'], type: 'search',
   334         searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
   335     // Ex command
   336     { keys: [':'], type: 'ex' }
   337   ];
   339   var Pos = CodeMirror.Pos;
   341   var Vim = function() {
   342     CodeMirror.defineOption('vimMode', false, function(cm, val) {
   343       if (val) {
   344         cm.setOption('keyMap', 'vim');
   345         cm.setOption('disableInput', true);
   346         CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
   347         cm.on('beforeSelectionChange', beforeSelectionChange);
   348         maybeInitVimState(cm);
   349         CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
   350       } else if (cm.state.vim) {
   351         cm.setOption('keyMap', 'default');
   352         cm.setOption('disableInput', false);
   353         cm.off('beforeSelectionChange', beforeSelectionChange);
   354         CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
   355         cm.state.vim = null;
   356       }
   357     });
   358     function beforeSelectionChange(cm, obj) {
   359       var vim = cm.state.vim;
   360       if (vim.insertMode || vim.exMode) return;
   362       var head = obj.ranges[0].head;
   363       var anchor = obj.ranges[0].anchor;
   364       if (head.ch && head.ch == cm.doc.getLine(head.line).length) {
   365         var pos = Pos(head.line, head.ch - 1);
   366         obj.update([{anchor: cursorEqual(head, anchor) ? pos : anchor,
   367                      head: pos}]);
   368       }
   369     }
   370     function getOnPasteFn(cm) {
   371       var vim = cm.state.vim;
   372       if (!vim.onPasteFn) {
   373         vim.onPasteFn = function() {
   374           if (!vim.insertMode) {
   375             cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
   376             actions.enterInsertMode(cm, {}, vim);
   377           }
   378         };
   379       }
   380       return vim.onPasteFn;
   381     }
   383     var numberRegex = /[\d]/;
   384     var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
   385     function makeKeyRange(start, size) {
   386       var keys = [];
   387       for (var i = start; i < start + size; i++) {
   388         keys.push(String.fromCharCode(i));
   389       }
   390       return keys;
   391     }
   392     var upperCaseAlphabet = makeKeyRange(65, 26);
   393     var lowerCaseAlphabet = makeKeyRange(97, 26);
   394     var numbers = makeKeyRange(48, 10);
   395     var specialSymbols = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;"\''.split('');
   396     var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace',
   397         'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter'];
   398     var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
   399     var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"']);
   401     function isLine(cm, line) {
   402       return line >= cm.firstLine() && line <= cm.lastLine();
   403     }
   404     function isLowerCase(k) {
   405       return (/^[a-z]$/).test(k);
   406     }
   407     function isMatchableSymbol(k) {
   408       return '()[]{}'.indexOf(k) != -1;
   409     }
   410     function isNumber(k) {
   411       return numberRegex.test(k);
   412     }
   413     function isUpperCase(k) {
   414       return (/^[A-Z]$/).test(k);
   415     }
   416     function isWhiteSpaceString(k) {
   417       return (/^\s*$/).test(k);
   418     }
   419     function inArray(val, arr) {
   420       for (var i = 0; i < arr.length; i++) {
   421         if (arr[i] == val) {
   422           return true;
   423         }
   424       }
   425       return false;
   426     }
   428     var options = {};
   429     function defineOption(name, defaultValue, type) {
   430       if (defaultValue === undefined) { throw Error('defaultValue is required'); }
   431       if (!type) { type = 'string'; }
   432       options[name] = {
   433         type: type,
   434         defaultValue: defaultValue
   435       };
   436       setOption(name, defaultValue);
   437     }
   439     function setOption(name, value) {
   440       var option = options[name];
   441       if (!option) {
   442         throw Error('Unknown option: ' + name);
   443       }
   444       if (option.type == 'boolean') {
   445         if (value && value !== true) {
   446           throw Error('Invalid argument: ' + name + '=' + value);
   447         } else if (value !== false) {
   448           // Boolean options are set to true if value is not defined.
   449           value = true;
   450         }
   451       }
   452       option.value = option.type == 'boolean' ? !!value : value;
   453     }
   455     function getOption(name) {
   456       var option = options[name];
   457       if (!option) {
   458         throw Error('Unknown option: ' + name);
   459       }
   460       return option.value;
   461     }
   463     var createCircularJumpList = function() {
   464       var size = 100;
   465       var pointer = -1;
   466       var head = 0;
   467       var tail = 0;
   468       var buffer = new Array(size);
   469       function add(cm, oldCur, newCur) {
   470         var current = pointer % size;
   471         var curMark = buffer[current];
   472         function useNextSlot(cursor) {
   473           var next = ++pointer % size;
   474           var trashMark = buffer[next];
   475           if (trashMark) {
   476             trashMark.clear();
   477           }
   478           buffer[next] = cm.setBookmark(cursor);
   479         }
   480         if (curMark) {
   481           var markPos = curMark.find();
   482           // avoid recording redundant cursor position
   483           if (markPos && !cursorEqual(markPos, oldCur)) {
   484             useNextSlot(oldCur);
   485           }
   486         } else {
   487           useNextSlot(oldCur);
   488         }
   489         useNextSlot(newCur);
   490         head = pointer;
   491         tail = pointer - size + 1;
   492         if (tail < 0) {
   493           tail = 0;
   494         }
   495       }
   496       function move(cm, offset) {
   497         pointer += offset;
   498         if (pointer > head) {
   499           pointer = head;
   500         } else if (pointer < tail) {
   501           pointer = tail;
   502         }
   503         var mark = buffer[(size + pointer) % size];
   504         // skip marks that are temporarily removed from text buffer
   505         if (mark && !mark.find()) {
   506           var inc = offset > 0 ? 1 : -1;
   507           var newCur;
   508           var oldCur = cm.getCursor();
   509           do {
   510             pointer += inc;
   511             mark = buffer[(size + pointer) % size];
   512             // skip marks that are the same as current position
   513             if (mark &&
   514                 (newCur = mark.find()) &&
   515                 !cursorEqual(oldCur, newCur)) {
   516               break;
   517             }
   518           } while (pointer < head && pointer > tail);
   519         }
   520         return mark;
   521       }
   522       return {
   523         cachedCursor: undefined, //used for # and * jumps
   524         add: add,
   525         move: move
   526       };
   527     };
   529     // Returns an object to track the changes associated insert mode.  It
   530     // clones the object that is passed in, or creates an empty object one if
   531     // none is provided.
   532     var createInsertModeChanges = function(c) {
   533       if (c) {
   534         // Copy construction
   535         return {
   536           changes: c.changes,
   537           expectCursorActivityForChange: c.expectCursorActivityForChange
   538         };
   539       }
   540       return {
   541         // Change list
   542         changes: [],
   543         // Set to true on change, false on cursorActivity.
   544         expectCursorActivityForChange: false
   545       };
   546     };
   548     function MacroModeState() {
   549       this.latestRegister = undefined;
   550       this.isPlaying = false;
   551       this.isRecording = false;
   552       this.onRecordingDone = undefined;
   553       this.lastInsertModeChanges = createInsertModeChanges();
   554     }
   555     MacroModeState.prototype = {
   556       exitMacroRecordMode: function() {
   557         var macroModeState = vimGlobalState.macroModeState;
   558         macroModeState.onRecordingDone(); // close dialog
   559         macroModeState.onRecordingDone = undefined;
   560         macroModeState.isRecording = false;
   561       },
   562       enterMacroRecordMode: function(cm, registerName) {
   563         var register =
   564             vimGlobalState.registerController.getRegister(registerName);
   565         if (register) {
   566           register.clear();
   567           this.latestRegister = registerName;
   568           this.onRecordingDone = cm.openDialog(
   569               '(recording)['+registerName+']', null, {bottom:true});
   570           this.isRecording = true;
   571         }
   572       }
   573     };
   575     function maybeInitVimState(cm) {
   576       if (!cm.state.vim) {
   577         // Store instance state in the CodeMirror object.
   578         cm.state.vim = {
   579           inputState: new InputState(),
   580           // Vim's input state that triggered the last edit, used to repeat
   581           // motions and operators with '.'.
   582           lastEditInputState: undefined,
   583           // Vim's action command before the last edit, used to repeat actions
   584           // with '.' and insert mode repeat.
   585           lastEditActionCommand: undefined,
   586           // When using jk for navigation, if you move from a longer line to a
   587           // shorter line, the cursor may clip to the end of the shorter line.
   588           // If j is pressed again and cursor goes to the next line, the
   589           // cursor should go back to its horizontal position on the longer
   590           // line if it can. This is to keep track of the horizontal position.
   591           lastHPos: -1,
   592           // Doing the same with screen-position for gj/gk
   593           lastHSPos: -1,
   594           // The last motion command run. Cleared if a non-motion command gets
   595           // executed in between.
   596           lastMotion: null,
   597           marks: {},
   598           insertMode: false,
   599           // Repeat count for changes made in insert mode, triggered by key
   600           // sequences like 3,i. Only exists when insertMode is true.
   601           insertModeRepeat: undefined,
   602           visualMode: false,
   603           // If we are in visual line mode. No effect if visualMode is false.
   604           visualLine: false,
   605           lastSelection: null
   606         };
   607       }
   608       return cm.state.vim;
   609     }
   610     var vimGlobalState;
   611     function resetVimGlobalState() {
   612       vimGlobalState = {
   613         // The current search query.
   614         searchQuery: null,
   615         // Whether we are searching backwards.
   616         searchIsReversed: false,
   617         jumpList: createCircularJumpList(),
   618         macroModeState: new MacroModeState,
   619         // Recording latest f, t, F or T motion command.
   620         lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
   621         registerController: new RegisterController({})
   622       };
   623       for (var optionName in options) {
   624         var option = options[optionName];
   625         option.value = option.defaultValue;
   626       }
   627     }
   629     var vimApi= {
   630       buildKeyMap: function() {
   631         // TODO: Convert keymap into dictionary format for fast lookup.
   632       },
   633       // Testing hook, though it might be useful to expose the register
   634       // controller anyways.
   635       getRegisterController: function() {
   636         return vimGlobalState.registerController;
   637       },
   638       // Testing hook.
   639       resetVimGlobalState_: resetVimGlobalState,
   641       // Testing hook.
   642       getVimGlobalState_: function() {
   643         return vimGlobalState;
   644       },
   646       // Testing hook.
   647       maybeInitVimState_: maybeInitVimState,
   649       InsertModeKey: InsertModeKey,
   650       map: function(lhs, rhs, ctx) {
   651         // Add user defined key bindings.
   652         exCommandDispatcher.map(lhs, rhs, ctx);
   653       },
   654       setOption: setOption,
   655       getOption: getOption,
   656       defineOption: defineOption,
   657       defineEx: function(name, prefix, func){
   658         if (name.indexOf(prefix) !== 0) {
   659           throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
   660         }
   661         exCommands[name]=func;
   662         exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
   663       },
   664       // This is the outermost function called by CodeMirror, after keys have
   665       // been mapped to their Vim equivalents.
   666       handleKey: function(cm, key) {
   667         var command;
   668         var vim = maybeInitVimState(cm);
   669         var macroModeState = vimGlobalState.macroModeState;
   670         if (macroModeState.isRecording) {
   671           if (key == 'q') {
   672             macroModeState.exitMacroRecordMode();
   673             vim.inputState = new InputState();
   674             return;
   675           }
   676         }
   677         if (key == '<Esc>') {
   678           // Clear input state and get back to normal mode.
   679           vim.inputState = new InputState();
   680           if (vim.visualMode) {
   681             exitVisualMode(cm);
   682           }
   683           return;
   684         }
   685         // Enter visual mode when the mouse selects text.
   686         if (!vim.visualMode &&
   687             !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) {
   688           vim.visualMode = true;
   689           vim.visualLine = false;
   690           CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
   691           cm.on('mousedown', exitVisualMode);
   692         }
   693         if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) {
   694           // Have to special case 0 since it's both a motion and a number.
   695           command = commandDispatcher.matchCommand(key, defaultKeymap, vim);
   696         }
   697         if (!command) {
   698           if (isNumber(key)) {
   699             // Increment count unless count is 0 and key is 0.
   700             vim.inputState.pushRepeatDigit(key);
   701           }
   702           if (macroModeState.isRecording) {
   703             logKey(macroModeState, key);
   704           }
   705           return;
   706         }
   707         if (command.type == 'keyToKey') {
   708           // TODO: prevent infinite recursion.
   709           for (var i = 0; i < command.toKeys.length; i++) {
   710             this.handleKey(cm, command.toKeys[i]);
   711           }
   712         } else {
   713           if (macroModeState.isRecording) {
   714             logKey(macroModeState, key);
   715           }
   716           commandDispatcher.processCommand(cm, vim, command);
   717         }
   718       },
   719       handleEx: function(cm, input) {
   720         exCommandDispatcher.processCommand(cm, input);
   721       }
   722     };
   724     // Represents the current input state.
   725     function InputState() {
   726       this.prefixRepeat = [];
   727       this.motionRepeat = [];
   729       this.operator = null;
   730       this.operatorArgs = null;
   731       this.motion = null;
   732       this.motionArgs = null;
   733       this.keyBuffer = []; // For matching multi-key commands.
   734       this.registerName = null; // Defaults to the unnamed register.
   735     }
   736     InputState.prototype.pushRepeatDigit = function(n) {
   737       if (!this.operator) {
   738         this.prefixRepeat = this.prefixRepeat.concat(n);
   739       } else {
   740         this.motionRepeat = this.motionRepeat.concat(n);
   741       }
   742     };
   743     InputState.prototype.getRepeat = function() {
   744       var repeat = 0;
   745       if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {
   746         repeat = 1;
   747         if (this.prefixRepeat.length > 0) {
   748           repeat *= parseInt(this.prefixRepeat.join(''), 10);
   749         }
   750         if (this.motionRepeat.length > 0) {
   751           repeat *= parseInt(this.motionRepeat.join(''), 10);
   752         }
   753       }
   754       return repeat;
   755     };
   757     /*
   758      * Register stores information about copy and paste registers.  Besides
   759      * text, a register must store whether it is linewise (i.e., when it is
   760      * pasted, should it insert itself into a new line, or should the text be
   761      * inserted at the cursor position.)
   762      */
   763     function Register(text, linewise) {
   764       this.clear();
   765       this.keyBuffer = [text || ''];
   766       this.insertModeChanges = [];
   767       this.linewise = !!linewise;
   768     }
   769     Register.prototype = {
   770       setText: function(text, linewise) {
   771         this.keyBuffer = [text || ''];
   772         this.linewise = !!linewise;
   773       },
   774       pushText: function(text, linewise) {
   775         // if this register has ever been set to linewise, use linewise.
   776         if (linewise || this.linewise) {
   777           this.keyBuffer.push('\n');
   778           this.linewise = true;
   779         }
   780         this.keyBuffer.push(text);
   781       },
   782       pushInsertModeChanges: function(changes) {
   783         this.insertModeChanges.push(createInsertModeChanges(changes));
   784       },
   785       clear: function() {
   786         this.keyBuffer = [];
   787         this.insertModeChanges = [];
   788         this.linewise = false;
   789       },
   790       toString: function() {
   791         return this.keyBuffer.join('');
   792       }
   793     };
   795     /*
   796      * vim registers allow you to keep many independent copy and paste buffers.
   797      * See http://usevim.com/2012/04/13/registers/ for an introduction.
   798      *
   799      * RegisterController keeps the state of all the registers.  An initial
   800      * state may be passed in.  The unnamed register '"' will always be
   801      * overridden.
   802      */
   803     function RegisterController(registers) {
   804       this.registers = registers;
   805       this.unnamedRegister = registers['"'] = new Register();
   806     }
   807     RegisterController.prototype = {
   808       pushText: function(registerName, operator, text, linewise) {
   809         if (linewise && text.charAt(0) == '\n') {
   810           text = text.slice(1) + '\n';
   811         }
   812         if (linewise && text.charAt(text.length - 1) !== '\n'){
   813           text += '\n';
   814         }
   815         // Lowercase and uppercase registers refer to the same register.
   816         // Uppercase just means append.
   817         var register = this.isValidRegister(registerName) ?
   818             this.getRegister(registerName) : null;
   819         // if no register/an invalid register was specified, things go to the
   820         // default registers
   821         if (!register) {
   822           switch (operator) {
   823             case 'yank':
   824               // The 0 register contains the text from the most recent yank.
   825               this.registers['0'] = new Register(text, linewise);
   826               break;
   827             case 'delete':
   828             case 'change':
   829               if (text.indexOf('\n') == -1) {
   830                 // Delete less than 1 line. Update the small delete register.
   831                 this.registers['-'] = new Register(text, linewise);
   832               } else {
   833                 // Shift down the contents of the numbered registers and put the
   834                 // deleted text into register 1.
   835                 this.shiftNumericRegisters_();
   836                 this.registers['1'] = new Register(text, linewise);
   837               }
   838               break;
   839           }
   840           // Make sure the unnamed register is set to what just happened
   841           this.unnamedRegister.setText(text, linewise);
   842           return;
   843         }
   845         // If we've gotten to this point, we've actually specified a register
   846         var append = isUpperCase(registerName);
   847         if (append) {
   848           register.append(text, linewise);
   849           // The unnamed register always has the same value as the last used
   850           // register.
   851           this.unnamedRegister.append(text, linewise);
   852         } else {
   853           register.setText(text, linewise);
   854           this.unnamedRegister.setText(text, linewise);
   855         }
   856       },
   857       // Gets the register named @name.  If one of @name doesn't already exist,
   858       // create it.  If @name is invalid, return the unnamedRegister.
   859       getRegister: function(name) {
   860         if (!this.isValidRegister(name)) {
   861           return this.unnamedRegister;
   862         }
   863         name = name.toLowerCase();
   864         if (!this.registers[name]) {
   865           this.registers[name] = new Register();
   866         }
   867         return this.registers[name];
   868       },
   869       isValidRegister: function(name) {
   870         return name && inArray(name, validRegisters);
   871       },
   872       shiftNumericRegisters_: function() {
   873         for (var i = 9; i >= 2; i--) {
   874           this.registers[i] = this.getRegister('' + (i - 1));
   875         }
   876       }
   877     };
   879     var commandDispatcher = {
   880       matchCommand: function(key, keyMap, vim) {
   881         var inputState = vim.inputState;
   882         var keys = inputState.keyBuffer.concat(key);
   883         var matchedCommands = [];
   884         var selectedCharacter;
   885         for (var i = 0; i < keyMap.length; i++) {
   886           var command = keyMap[i];
   887           if (matchKeysPartial(keys, command.keys)) {
   888             if (inputState.operator && command.type == 'action') {
   889               // Ignore matched action commands after an operator. Operators
   890               // only operate on motions. This check is really for text
   891               // objects since aW, a[ etcs conflicts with a.
   892               continue;
   893             }
   894             // Match commands that take <character> as an argument.
   895             if (command.keys[keys.length - 1] == 'character') {
   896               selectedCharacter = keys[keys.length - 1];
   897               if (selectedCharacter.length>1){
   898                 switch(selectedCharacter){
   899                   case '<CR>':
   900                     selectedCharacter='\n';
   901                     break;
   902                   case '<Space>':
   903                     selectedCharacter=' ';
   904                     break;
   905                   default:
   906                     continue;
   907                 }
   908               }
   909             }
   910             // Add the command to the list of matched commands. Choose the best
   911             // command later.
   912             matchedCommands.push(command);
   913           }
   914         }
   916         // Returns the command if it is a full match, or null if not.
   917         function getFullyMatchedCommandOrNull(command) {
   918           if (keys.length < command.keys.length) {
   919             // Matches part of a multi-key command. Buffer and wait for next
   920             // stroke.
   921             inputState.keyBuffer.push(key);
   922             return null;
   923           } else {
   924             if (command.keys[keys.length - 1] == 'character') {
   925               inputState.selectedCharacter = selectedCharacter;
   926             }
   927             // Clear the buffer since a full match was found.
   928             inputState.keyBuffer = [];
   929             return command;
   930           }
   931         }
   933         if (!matchedCommands.length) {
   934           // Clear the buffer since there were no matches.
   935           inputState.keyBuffer = [];
   936           return null;
   937         } else if (matchedCommands.length == 1) {
   938           return getFullyMatchedCommandOrNull(matchedCommands[0]);
   939         } else {
   940           // Find the best match in the list of matchedCommands.
   941           var context = vim.visualMode ? 'visual' : 'normal';
   942           var bestMatch; // Default to first in the list.
   943           for (var i = 0; i < matchedCommands.length; i++) {
   944             var current = matchedCommands[i];
   945             if (current.context == context) {
   946               bestMatch = current;
   947               break;
   948             } else if (!bestMatch && !current.context) {
   949               // Only set an imperfect match to best match if no best match is
   950               // set and the imperfect match is not restricted to another
   951               // context.
   952               bestMatch = current;
   953             }
   954           }
   955           return getFullyMatchedCommandOrNull(bestMatch);
   956         }
   957       },
   958       processCommand: function(cm, vim, command) {
   959         vim.inputState.repeatOverride = command.repeatOverride;
   960         switch (command.type) {
   961           case 'motion':
   962             this.processMotion(cm, vim, command);
   963             break;
   964           case 'operator':
   965             this.processOperator(cm, vim, command);
   966             break;
   967           case 'operatorMotion':
   968             this.processOperatorMotion(cm, vim, command);
   969             break;
   970           case 'action':
   971             this.processAction(cm, vim, command);
   972             break;
   973           case 'search':
   974             this.processSearch(cm, vim, command);
   975             break;
   976           case 'ex':
   977           case 'keyToEx':
   978             this.processEx(cm, vim, command);
   979             break;
   980           default:
   981             break;
   982         }
   983       },
   984       processMotion: function(cm, vim, command) {
   985         vim.inputState.motion = command.motion;
   986         vim.inputState.motionArgs = copyArgs(command.motionArgs);
   987         this.evalInput(cm, vim);
   988       },
   989       processOperator: function(cm, vim, command) {
   990         var inputState = vim.inputState;
   991         if (inputState.operator) {
   992           if (inputState.operator == command.operator) {
   993             // Typing an operator twice like 'dd' makes the operator operate
   994             // linewise
   995             inputState.motion = 'expandToLine';
   996             inputState.motionArgs = { linewise: true };
   997             this.evalInput(cm, vim);
   998             return;
   999           } else {
  1000             // 2 different operators in a row doesn't make sense.
  1001             vim.inputState = new InputState();
  1004         inputState.operator = command.operator;
  1005         inputState.operatorArgs = copyArgs(command.operatorArgs);
  1006         if (vim.visualMode) {
  1007           // Operating on a selection in visual mode. We don't need a motion.
  1008           this.evalInput(cm, vim);
  1010       },
  1011       processOperatorMotion: function(cm, vim, command) {
  1012         var visualMode = vim.visualMode;
  1013         var operatorMotionArgs = copyArgs(command.operatorMotionArgs);
  1014         if (operatorMotionArgs) {
  1015           // Operator motions may have special behavior in visual mode.
  1016           if (visualMode && operatorMotionArgs.visualLine) {
  1017             vim.visualLine = true;
  1020         this.processOperator(cm, vim, command);
  1021         if (!visualMode) {
  1022           this.processMotion(cm, vim, command);
  1024       },
  1025       processAction: function(cm, vim, command) {
  1026         var inputState = vim.inputState;
  1027         var repeat = inputState.getRepeat();
  1028         var repeatIsExplicit = !!repeat;
  1029         var actionArgs = copyArgs(command.actionArgs) || {};
  1030         if (inputState.selectedCharacter) {
  1031           actionArgs.selectedCharacter = inputState.selectedCharacter;
  1033         // Actions may or may not have motions and operators. Do these first.
  1034         if (command.operator) {
  1035           this.processOperator(cm, vim, command);
  1037         if (command.motion) {
  1038           this.processMotion(cm, vim, command);
  1040         if (command.motion || command.operator) {
  1041           this.evalInput(cm, vim);
  1043         actionArgs.repeat = repeat || 1;
  1044         actionArgs.repeatIsExplicit = repeatIsExplicit;
  1045         actionArgs.registerName = inputState.registerName;
  1046         vim.inputState = new InputState();
  1047         vim.lastMotion = null;
  1048         if (command.isEdit) {
  1049           this.recordLastEdit(vim, inputState, command);
  1051         actions[command.action](cm, actionArgs, vim);
  1052       },
  1053       processSearch: function(cm, vim, command) {
  1054         if (!cm.getSearchCursor) {
  1055           // Search depends on SearchCursor.
  1056           return;
  1058         var forward = command.searchArgs.forward;
  1059         getSearchState(cm).setReversed(!forward);
  1060         var promptPrefix = (forward) ? '/' : '?';
  1061         var originalQuery = getSearchState(cm).getQuery();
  1062         var originalScrollPos = cm.getScrollInfo();
  1063         function handleQuery(query, ignoreCase, smartCase) {
  1064           try {
  1065             updateSearchQuery(cm, query, ignoreCase, smartCase);
  1066           } catch (e) {
  1067             showConfirm(cm, 'Invalid regex: ' + query);
  1068             return;
  1070           commandDispatcher.processMotion(cm, vim, {
  1071             type: 'motion',
  1072             motion: 'findNext',
  1073             motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }
  1074           });
  1076         function onPromptClose(query) {
  1077           cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
  1078           handleQuery(query, true /** ignoreCase */, true /** smartCase */);
  1080         function onPromptKeyUp(_e, query) {
  1081           var parsedQuery;
  1082           try {
  1083             parsedQuery = updateSearchQuery(cm, query,
  1084                 true /** ignoreCase */, true /** smartCase */);
  1085           } catch (e) {
  1086             // Swallow bad regexes for incremental search.
  1088           if (parsedQuery) {
  1089             cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);
  1090           } else {
  1091             clearSearchHighlight(cm);
  1092             cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
  1095         function onPromptKeyDown(e, _query, close) {
  1096           var keyName = CodeMirror.keyName(e);
  1097           if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
  1098             updateSearchQuery(cm, originalQuery);
  1099             clearSearchHighlight(cm);
  1100             cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
  1102             CodeMirror.e_stop(e);
  1103             close();
  1104             cm.focus();
  1107         switch (command.searchArgs.querySrc) {
  1108           case 'prompt':
  1109             showPrompt(cm, {
  1110                 onClose: onPromptClose,
  1111                 prefix: promptPrefix,
  1112                 desc: searchPromptDesc,
  1113                 onKeyUp: onPromptKeyUp,
  1114                 onKeyDown: onPromptKeyDown
  1115             });
  1116             break;
  1117           case 'wordUnderCursor':
  1118             var word = expandWordUnderCursor(cm, false /** inclusive */,
  1119                 true /** forward */, false /** bigWord */,
  1120                 true /** noSymbol */);
  1121             var isKeyword = true;
  1122             if (!word) {
  1123               word = expandWordUnderCursor(cm, false /** inclusive */,
  1124                   true /** forward */, false /** bigWord */,
  1125                   false /** noSymbol */);
  1126               isKeyword = false;
  1128             if (!word) {
  1129               return;
  1131             var query = cm.getLine(word.start.line).substring(word.start.ch,
  1132                 word.end.ch);
  1133             if (isKeyword) {
  1134               query = '\\b' + query + '\\b';
  1135             } else {
  1136               query = escapeRegex(query);
  1139             // cachedCursor is used to save the old position of the cursor
  1140             // when * or # causes vim to seek for the nearest word and shift
  1141             // the cursor before entering the motion.
  1142             vimGlobalState.jumpList.cachedCursor = cm.getCursor();
  1143             cm.setCursor(word.start);
  1145             handleQuery(query, true /** ignoreCase */, false /** smartCase */);
  1146             break;
  1148       },
  1149       processEx: function(cm, vim, command) {
  1150         function onPromptClose(input) {
  1151           // Give the prompt some time to close so that if processCommand shows
  1152           // an error, the elements don't overlap.
  1153           exCommandDispatcher.processCommand(cm, input);
  1155         function onPromptKeyDown(e, _input, close) {
  1156           var keyName = CodeMirror.keyName(e);
  1157           if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
  1158             CodeMirror.e_stop(e);
  1159             close();
  1160             cm.focus();
  1163         if (command.type == 'keyToEx') {
  1164           // Handle user defined Ex to Ex mappings
  1165           exCommandDispatcher.processCommand(cm, command.exArgs.input);
  1166         } else {
  1167           if (vim.visualMode) {
  1168             showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
  1169                 onKeyDown: onPromptKeyDown});
  1170           } else {
  1171             showPrompt(cm, { onClose: onPromptClose, prefix: ':',
  1172                 onKeyDown: onPromptKeyDown});
  1175       },
  1176       evalInput: function(cm, vim) {
  1177         // If the motion comand is set, execute both the operator and motion.
  1178         // Otherwise return.
  1179         var inputState = vim.inputState;
  1180         var motion = inputState.motion;
  1181         var motionArgs = inputState.motionArgs || {};
  1182         var operator = inputState.operator;
  1183         var operatorArgs = inputState.operatorArgs || {};
  1184         var registerName = inputState.registerName;
  1185         var selectionEnd = copyCursor(cm.getCursor('head'));
  1186         var selectionStart = copyCursor(cm.getCursor('anchor'));
  1187         // The difference between cur and selection cursors are that cur is
  1188         // being operated on and ignores that there is a selection.
  1189         var curStart = copyCursor(selectionEnd);
  1190         var curOriginal = copyCursor(curStart);
  1191         var curEnd;
  1192         var repeat;
  1193         if (operator) {
  1194           this.recordLastEdit(vim, inputState);
  1196         if (inputState.repeatOverride !== undefined) {
  1197           // If repeatOverride is specified, that takes precedence over the
  1198           // input state's repeat. Used by Ex mode and can be user defined.
  1199           repeat = inputState.repeatOverride;
  1200         } else {
  1201           repeat = inputState.getRepeat();
  1203         if (repeat > 0 && motionArgs.explicitRepeat) {
  1204           motionArgs.repeatIsExplicit = true;
  1205         } else if (motionArgs.noRepeat ||
  1206             (!motionArgs.explicitRepeat && repeat === 0)) {
  1207           repeat = 1;
  1208           motionArgs.repeatIsExplicit = false;
  1210         if (inputState.selectedCharacter) {
  1211           // If there is a character input, stick it in all of the arg arrays.
  1212           motionArgs.selectedCharacter = operatorArgs.selectedCharacter =
  1213               inputState.selectedCharacter;
  1215         motionArgs.repeat = repeat;
  1216         vim.inputState = new InputState();
  1217         if (motion) {
  1218           var motionResult = motions[motion](cm, motionArgs, vim);
  1219           vim.lastMotion = motions[motion];
  1220           if (!motionResult) {
  1221             return;
  1223           if (motionArgs.toJumplist) {
  1224             var jumpList = vimGlobalState.jumpList;
  1225             // if the current motion is # or *, use cachedCursor
  1226             var cachedCursor = jumpList.cachedCursor;
  1227             if (cachedCursor) {
  1228               recordJumpPosition(cm, cachedCursor, motionResult);
  1229               delete jumpList.cachedCursor;
  1230             } else {
  1231               recordJumpPosition(cm, curOriginal, motionResult);
  1234           if (motionResult instanceof Array) {
  1235             curStart = motionResult[0];
  1236             curEnd = motionResult[1];
  1237           } else {
  1238             curEnd = motionResult;
  1240           // TODO: Handle null returns from motion commands better.
  1241           if (!curEnd) {
  1242             curEnd = Pos(curStart.line, curStart.ch);
  1244           if (vim.visualMode) {
  1245             // Check if the selection crossed over itself. Will need to shift
  1246             // the start point if that happened.
  1247             if (cursorIsBefore(selectionStart, selectionEnd) &&
  1248                 (cursorEqual(selectionStart, curEnd) ||
  1249                     cursorIsBefore(curEnd, selectionStart))) {
  1250               // The end of the selection has moved from after the start to
  1251               // before the start. We will shift the start right by 1.
  1252               selectionStart.ch += 1;
  1253             } else if (cursorIsBefore(selectionEnd, selectionStart) &&
  1254                 (cursorEqual(selectionStart, curEnd) ||
  1255                     cursorIsBefore(selectionStart, curEnd))) {
  1256               // The opposite happened. We will shift the start left by 1.
  1257               selectionStart.ch -= 1;
  1259             selectionEnd = curEnd;
  1260             selectionStart = (motionResult instanceof Array) ? curStart : selectionStart;
  1261             if (vim.visualLine) {
  1262               if (cursorIsBefore(selectionStart, selectionEnd)) {
  1263                 selectionStart.ch = 0;
  1265                 var lastLine = cm.lastLine();
  1266                 if (selectionEnd.line > lastLine) {
  1267                   selectionEnd.line = lastLine;
  1269                 selectionEnd.ch = lineLength(cm, selectionEnd.line);
  1270               } else {
  1271                 selectionEnd.ch = 0;
  1272                 selectionStart.ch = lineLength(cm, selectionStart.line);
  1275             cm.setSelection(selectionStart, selectionEnd);
  1276             updateMark(cm, vim, '<',
  1277                 cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
  1278                     : selectionEnd);
  1279             updateMark(cm, vim, '>',
  1280                 cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd
  1281                     : selectionStart);
  1282           } else if (!operator) {
  1283             curEnd = clipCursorToContent(cm, curEnd);
  1284             cm.setCursor(curEnd.line, curEnd.ch);
  1288         if (operator) {
  1289           var inverted = false;
  1290           vim.lastMotion = null;
  1291           operatorArgs.repeat = repeat; // Indent in visual mode needs this.
  1292           if (vim.visualMode) {
  1293             curStart = selectionStart;
  1294             curEnd = selectionEnd;
  1295             motionArgs.inclusive = true;
  1297           // Swap start and end if motion was backward.
  1298           if (cursorIsBefore(curEnd, curStart)) {
  1299             var tmp = curStart;
  1300             curStart = curEnd;
  1301             curEnd = tmp;
  1302             inverted = true;
  1304           if (motionArgs.inclusive && !(vim.visualMode && inverted)) {
  1305             // Move the selection end one to the right to include the last
  1306             // character.
  1307             curEnd.ch++;
  1309           var linewise = motionArgs.linewise ||
  1310               (vim.visualMode && vim.visualLine);
  1311           if (linewise) {
  1312             // Expand selection to entire line.
  1313             expandSelectionToLine(cm, curStart, curEnd);
  1314           } else if (motionArgs.forward) {
  1315             // Clip to trailing newlines only if the motion goes forward.
  1316             clipToLine(cm, curStart, curEnd);
  1318           operatorArgs.registerName = registerName;
  1319           // Keep track of linewise as it affects how paste and change behave.
  1320           operatorArgs.linewise = linewise;
  1321           operators[operator](cm, operatorArgs, vim, curStart,
  1322               curEnd, curOriginal);
  1323           if (vim.visualMode) {
  1324             exitVisualMode(cm);
  1327       },
  1328       recordLastEdit: function(vim, inputState, actionCommand) {
  1329         var macroModeState = vimGlobalState.macroModeState;
  1330         if (macroModeState.isPlaying) { return; }
  1331         vim.lastEditInputState = inputState;
  1332         vim.lastEditActionCommand = actionCommand;
  1333         macroModeState.lastInsertModeChanges.changes = [];
  1334         macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
  1336     };
  1338     /**
  1339      * typedef {Object{line:number,ch:number}} Cursor An object containing the
  1340      *     position of the cursor.
  1341      */
  1342     // All of the functions below return Cursor objects.
  1343     var motions = {
  1344       moveToTopLine: function(cm, motionArgs) {
  1345         var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
  1346         return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
  1347       },
  1348       moveToMiddleLine: function(cm) {
  1349         var range = getUserVisibleLines(cm);
  1350         var line = Math.floor((range.top + range.bottom) * 0.5);
  1351         return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
  1352       },
  1353       moveToBottomLine: function(cm, motionArgs) {
  1354         var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
  1355         return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
  1356       },
  1357       expandToLine: function(cm, motionArgs) {
  1358         // Expands forward to end of line, and then to next line if repeat is
  1359         // >1. Does not handle backward motion!
  1360         var cur = cm.getCursor();
  1361         return Pos(cur.line + motionArgs.repeat - 1, Infinity);
  1362       },
  1363       findNext: function(cm, motionArgs) {
  1364         var state = getSearchState(cm);
  1365         var query = state.getQuery();
  1366         if (!query) {
  1367           return;
  1369         var prev = !motionArgs.forward;
  1370         // If search is initiated with ? instead of /, negate direction.
  1371         prev = (state.isReversed()) ? !prev : prev;
  1372         highlightSearchMatches(cm, query);
  1373         return findNext(cm, prev/** prev */, query, motionArgs.repeat);
  1374       },
  1375       goToMark: function(_cm, motionArgs, vim) {
  1376         var mark = vim.marks[motionArgs.selectedCharacter];
  1377         if (mark) {
  1378           return mark.find();
  1380         return null;
  1381       },
  1382       moveToOtherHighlightedEnd: function(cm) {
  1383         var curEnd = copyCursor(cm.getCursor('head'));
  1384         var curStart = copyCursor(cm.getCursor('anchor'));
  1385         if (cursorIsBefore(curStart, curEnd)) {
  1386            curEnd.ch += 1;
  1387         } else if (cursorIsBefore(curEnd, curStart)) {
  1388            curStart.ch -= 1;
  1390         return ([curEnd,curStart]);
  1391       },
  1392       jumpToMark: function(cm, motionArgs, vim) {
  1393         var best = cm.getCursor();
  1394         for (var i = 0; i < motionArgs.repeat; i++) {
  1395           var cursor = best;
  1396           for (var key in vim.marks) {
  1397             if (!isLowerCase(key)) {
  1398               continue;
  1400             var mark = vim.marks[key].find();
  1401             var isWrongDirection = (motionArgs.forward) ?
  1402               cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);
  1404             if (isWrongDirection) {
  1405               continue;
  1407             if (motionArgs.linewise && (mark.line == cursor.line)) {
  1408               continue;
  1411             var equal = cursorEqual(cursor, best);
  1412             var between = (motionArgs.forward) ?
  1413               cusrorIsBetween(cursor, mark, best) :
  1414               cusrorIsBetween(best, mark, cursor);
  1416             if (equal || between) {
  1417               best = mark;
  1422         if (motionArgs.linewise) {
  1423           // Vim places the cursor on the first non-whitespace character of
  1424           // the line if there is one, else it places the cursor at the end
  1425           // of the line, regardless of whether a mark was found.
  1426           best = Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line)));
  1428         return best;
  1429       },
  1430       moveByCharacters: function(cm, motionArgs) {
  1431         var cur = cm.getCursor();
  1432         var repeat = motionArgs.repeat;
  1433         var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
  1434         return Pos(cur.line, ch);
  1435       },
  1436       moveByLines: function(cm, motionArgs, vim) {
  1437         var cur = cm.getCursor();
  1438         var endCh = cur.ch;
  1439         // Depending what our last motion was, we may want to do different
  1440         // things. If our last motion was moving vertically, we want to
  1441         // preserve the HPos from our last horizontal move.  If our last motion
  1442         // was going to the end of a line, moving vertically we should go to
  1443         // the end of the line, etc.
  1444         switch (vim.lastMotion) {
  1445           case this.moveByLines:
  1446           case this.moveByDisplayLines:
  1447           case this.moveByScroll:
  1448           case this.moveToColumn:
  1449           case this.moveToEol:
  1450             endCh = vim.lastHPos;
  1451             break;
  1452           default:
  1453             vim.lastHPos = endCh;
  1455         var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
  1456         var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
  1457         var first = cm.firstLine();
  1458         var last = cm.lastLine();
  1459         // Vim cancels linewise motions that start on an edge and move beyond
  1460         // that edge. It does not cancel motions that do not start on an edge.
  1461         if ((line < first && cur.line == first) ||
  1462             (line > last && cur.line == last)) {
  1463           return;
  1465         if (motionArgs.toFirstChar){
  1466           endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
  1467           vim.lastHPos = endCh;
  1469         vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left;
  1470         return Pos(line, endCh);
  1471       },
  1472       moveByDisplayLines: function(cm, motionArgs, vim) {
  1473         var cur = cm.getCursor();
  1474         switch (vim.lastMotion) {
  1475           case this.moveByDisplayLines:
  1476           case this.moveByScroll:
  1477           case this.moveByLines:
  1478           case this.moveToColumn:
  1479           case this.moveToEol:
  1480             break;
  1481           default:
  1482             vim.lastHSPos = cm.charCoords(cur,'div').left;
  1484         var repeat = motionArgs.repeat;
  1485         var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);
  1486         if (res.hitSide) {
  1487           if (motionArgs.forward) {
  1488             var lastCharCoords = cm.charCoords(res, 'div');
  1489             var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };
  1490             var res = cm.coordsChar(goalCoords, 'div');
  1491           } else {
  1492             var resCoords = cm.charCoords(Pos(cm.firstLine(), 0), 'div');
  1493             resCoords.left = vim.lastHSPos;
  1494             res = cm.coordsChar(resCoords, 'div');
  1497         vim.lastHPos = res.ch;
  1498         return res;
  1499       },
  1500       moveByPage: function(cm, motionArgs) {
  1501         // CodeMirror only exposes functions that move the cursor page down, so
  1502         // doing this bad hack to move the cursor and move it back. evalInput
  1503         // will move the cursor to where it should be in the end.
  1504         var curStart = cm.getCursor();
  1505         var repeat = motionArgs.repeat;
  1506         cm.moveV((motionArgs.forward ? repeat : -repeat), 'page');
  1507         var curEnd = cm.getCursor();
  1508         cm.setCursor(curStart);
  1509         return curEnd;
  1510       },
  1511       moveByParagraph: function(cm, motionArgs) {
  1512         var line = cm.getCursor().line;
  1513         var repeat = motionArgs.repeat;
  1514         var inc = motionArgs.forward ? 1 : -1;
  1515         for (var i = 0; i < repeat; i++) {
  1516           if ((!motionArgs.forward && line === cm.firstLine() ) ||
  1517               (motionArgs.forward && line == cm.lastLine())) {
  1518             break;
  1520           line += inc;
  1521           while (line !== cm.firstLine() && line != cm.lastLine() && cm.getLine(line)) {
  1522             line += inc;
  1525         return Pos(line, 0);
  1526       },
  1527       moveByScroll: function(cm, motionArgs, vim) {
  1528         var scrollbox = cm.getScrollInfo();
  1529         var curEnd = null;
  1530         var repeat = motionArgs.repeat;
  1531         if (!repeat) {
  1532           repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
  1534         var orig = cm.charCoords(cm.getCursor(), 'local');
  1535         motionArgs.repeat = repeat;
  1536         var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim);
  1537         if (!curEnd) {
  1538           return null;
  1540         var dest = cm.charCoords(curEnd, 'local');
  1541         cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
  1542         return curEnd;
  1543       },
  1544       moveByWords: function(cm, motionArgs) {
  1545         return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward,
  1546             !!motionArgs.wordEnd, !!motionArgs.bigWord);
  1547       },
  1548       moveTillCharacter: function(cm, motionArgs) {
  1549         var repeat = motionArgs.repeat;
  1550         var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
  1551             motionArgs.selectedCharacter);
  1552         var increment = motionArgs.forward ? -1 : 1;
  1553         recordLastCharacterSearch(increment, motionArgs);
  1554         if (!curEnd) return null;
  1555         curEnd.ch += increment;
  1556         return curEnd;
  1557       },
  1558       moveToCharacter: function(cm, motionArgs) {
  1559         var repeat = motionArgs.repeat;
  1560         recordLastCharacterSearch(0, motionArgs);
  1561         return moveToCharacter(cm, repeat, motionArgs.forward,
  1562             motionArgs.selectedCharacter) || cm.getCursor();
  1563       },
  1564       moveToSymbol: function(cm, motionArgs) {
  1565         var repeat = motionArgs.repeat;
  1566         return findSymbol(cm, repeat, motionArgs.forward,
  1567             motionArgs.selectedCharacter) || cm.getCursor();
  1568       },
  1569       moveToColumn: function(cm, motionArgs, vim) {
  1570         var repeat = motionArgs.repeat;
  1571         // repeat is equivalent to which column we want to move to!
  1572         vim.lastHPos = repeat - 1;
  1573         vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left;
  1574         return moveToColumn(cm, repeat);
  1575       },
  1576       moveToEol: function(cm, motionArgs, vim) {
  1577         var cur = cm.getCursor();
  1578         vim.lastHPos = Infinity;
  1579         var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity);
  1580         var end=cm.clipPos(retval);
  1581         end.ch--;
  1582         vim.lastHSPos = cm.charCoords(end,'div').left;
  1583         return retval;
  1584       },
  1585       moveToFirstNonWhiteSpaceCharacter: function(cm) {
  1586         // Go to the start of the line where the text begins, or the end for
  1587         // whitespace-only lines
  1588         var cursor = cm.getCursor();
  1589         return Pos(cursor.line,
  1590                    findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));
  1591       },
  1592       moveToMatchedSymbol: function(cm) {
  1593         var cursor = cm.getCursor();
  1594         var line = cursor.line;
  1595         var ch = cursor.ch;
  1596         var lineText = cm.getLine(line);
  1597         var symbol;
  1598         var startContext = cm.getTokenAt(cursor).type;
  1599         var startCtxLevel = getContextLevel(startContext);
  1600         do {
  1601           symbol = lineText.charAt(ch++);
  1602           if (symbol && isMatchableSymbol(symbol)) {
  1603             var endContext = cm.getTokenAt(Pos(line, ch)).type;
  1604             var endCtxLevel = getContextLevel(endContext);
  1605             if (startCtxLevel >= endCtxLevel) {
  1606               break;
  1609         } while (symbol);
  1610         if (symbol) {
  1611           return findMatchedSymbol(cm, Pos(line, ch-1), symbol);
  1612         } else {
  1613           return cursor;
  1615       },
  1616       moveToStartOfLine: function(cm) {
  1617         var cursor = cm.getCursor();
  1618         return Pos(cursor.line, 0);
  1619       },
  1620       moveToLineOrEdgeOfDocument: function(cm, motionArgs) {
  1621         var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
  1622         if (motionArgs.repeatIsExplicit) {
  1623           lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
  1625         return Pos(lineNum,
  1626                    findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)));
  1627       },
  1628       textObjectManipulation: function(cm, motionArgs) {
  1629         // TODO: lots of possible exceptions that can be thrown here. Try da(
  1630         //     outside of a () block.
  1632         // TODO: adding <> >< to this map doesn't work, presumably because
  1633         // they're operators
  1634         var mirroredPairs = {'(': ')', ')': '(',
  1635                              '{': '}', '}': '{',
  1636                              '[': ']', ']': '['};
  1637         var selfPaired = {'\'': true, '"': true};
  1639         var character = motionArgs.selectedCharacter;
  1641         // Inclusive is the difference between a and i
  1642         // TODO: Instead of using the additional text object map to perform text
  1643         //     object operations, merge the map into the defaultKeyMap and use
  1644         //     motionArgs to define behavior. Define separate entries for 'aw',
  1645         //     'iw', 'a[', 'i[', etc.
  1646         var inclusive = !motionArgs.textObjectInner;
  1648         var tmp;
  1649         if (mirroredPairs[character]) {
  1650           tmp = selectCompanionObject(cm, mirroredPairs[character], inclusive);
  1651         } else if (selfPaired[character]) {
  1652           tmp = findBeginningAndEnd(cm, character, inclusive);
  1653         } else if (character === 'W') {
  1654           tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
  1655                                                      true /** bigWord */);
  1656         } else if (character === 'w') {
  1657           tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
  1658                                                      false /** bigWord */);
  1659         } else {
  1660           // No text object defined for this, don't move.
  1661           return null;
  1664         return [tmp.start, tmp.end];
  1665       },
  1667       repeatLastCharacterSearch: function(cm, motionArgs) {
  1668         var lastSearch = vimGlobalState.lastChararacterSearch;
  1669         var repeat = motionArgs.repeat;
  1670         var forward = motionArgs.forward === lastSearch.forward;
  1671         var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
  1672         cm.moveH(-increment, 'char');
  1673         motionArgs.inclusive = forward ? true : false;
  1674         var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
  1675         if (!curEnd) {
  1676           cm.moveH(increment, 'char');
  1677           return cm.getCursor();
  1679         curEnd.ch += increment;
  1680         return curEnd;
  1682     };
  1684     var operators = {
  1685       change: function(cm, operatorArgs, _vim, curStart, curEnd) {
  1686         vimGlobalState.registerController.pushText(
  1687             operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd),
  1688             operatorArgs.linewise);
  1689         if (operatorArgs.linewise) {
  1690           // Push the next line back down, if there is a next line.
  1691           var replacement = curEnd.line > cm.lastLine() ? '' : '\n';
  1692           cm.replaceRange(replacement, curStart, curEnd);
  1693           cm.indentLine(curStart.line, 'smart');
  1694           // null ch so setCursor moves to end of line.
  1695           curStart.ch = null;
  1696         } else {
  1697           // Exclude trailing whitespace if the range is not all whitespace.
  1698           var text = cm.getRange(curStart, curEnd);
  1699           if (!isWhiteSpaceString(text)) {
  1700             var match = (/\s+$/).exec(text);
  1701             if (match) {
  1702               curEnd = offsetCursor(curEnd, 0, - match[0].length);
  1705           cm.replaceRange('', curStart, curEnd);
  1707         actions.enterInsertMode(cm, {}, cm.state.vim);
  1708         cm.setCursor(curStart);
  1709       },
  1710       // delete is a javascript keyword.
  1711       'delete': function(cm, operatorArgs, _vim, curStart, curEnd) {
  1712         // If the ending line is past the last line, inclusive, instead of
  1713         // including the trailing \n, include the \n before the starting line
  1714         if (operatorArgs.linewise &&
  1715             curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) {
  1716           curStart.line--;
  1717           curStart.ch = lineLength(cm, curStart.line);
  1719         vimGlobalState.registerController.pushText(
  1720             operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd),
  1721             operatorArgs.linewise);
  1722         cm.replaceRange('', curStart, curEnd);
  1723         if (operatorArgs.linewise) {
  1724           cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
  1725         } else {
  1726           cm.setCursor(curStart);
  1728       },
  1729       indent: function(cm, operatorArgs, vim, curStart, curEnd) {
  1730         var startLine = curStart.line;
  1731         var endLine = curEnd.line;
  1732         // In visual mode, n> shifts the selection right n times, instead of
  1733         // shifting n lines right once.
  1734         var repeat = (vim.visualMode) ? operatorArgs.repeat : 1;
  1735         if (operatorArgs.linewise) {
  1736           // The only way to delete a newline is to delete until the start of
  1737           // the next line, so in linewise mode evalInput will include the next
  1738           // line. We don't want this in indent, so we go back a line.
  1739           endLine--;
  1741         for (var i = startLine; i <= endLine; i++) {
  1742           for (var j = 0; j < repeat; j++) {
  1743             cm.indentLine(i, operatorArgs.indentRight);
  1746         cm.setCursor(curStart);
  1747         cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
  1748       },
  1749       swapcase: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
  1750         var toSwap = cm.getRange(curStart, curEnd);
  1751         var swapped = '';
  1752         for (var i = 0; i < toSwap.length; i++) {
  1753           var character = toSwap.charAt(i);
  1754           swapped += isUpperCase(character) ? character.toLowerCase() :
  1755               character.toUpperCase();
  1757         cm.replaceRange(swapped, curStart, curEnd);
  1758         if (!operatorArgs.shouldMoveCursor) {
  1759           cm.setCursor(curOriginal);
  1761       },
  1762       yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
  1763         vimGlobalState.registerController.pushText(
  1764             operatorArgs.registerName, 'yank',
  1765             cm.getRange(curStart, curEnd), operatorArgs.linewise);
  1766         cm.setCursor(curOriginal);
  1768     };
  1770     var actions = {
  1771       jumpListWalk: function(cm, actionArgs, vim) {
  1772         if (vim.visualMode) {
  1773           return;
  1775         var repeat = actionArgs.repeat;
  1776         var forward = actionArgs.forward;
  1777         var jumpList = vimGlobalState.jumpList;
  1779         var mark = jumpList.move(cm, forward ? repeat : -repeat);
  1780         var markPos = mark ? mark.find() : undefined;
  1781         markPos = markPos ? markPos : cm.getCursor();
  1782         cm.setCursor(markPos);
  1783       },
  1784       scroll: function(cm, actionArgs, vim) {
  1785         if (vim.visualMode) {
  1786           return;
  1788         var repeat = actionArgs.repeat || 1;
  1789         var lineHeight = cm.defaultTextHeight();
  1790         var top = cm.getScrollInfo().top;
  1791         var delta = lineHeight * repeat;
  1792         var newPos = actionArgs.forward ? top + delta : top - delta;
  1793         var cursor = copyCursor(cm.getCursor());
  1794         var cursorCoords = cm.charCoords(cursor, 'local');
  1795         if (actionArgs.forward) {
  1796           if (newPos > cursorCoords.top) {
  1797              cursor.line += (newPos - cursorCoords.top) / lineHeight;
  1798              cursor.line = Math.ceil(cursor.line);
  1799              cm.setCursor(cursor);
  1800              cursorCoords = cm.charCoords(cursor, 'local');
  1801              cm.scrollTo(null, cursorCoords.top);
  1802           } else {
  1803              // Cursor stays within bounds.  Just reposition the scroll window.
  1804              cm.scrollTo(null, newPos);
  1806         } else {
  1807           var newBottom = newPos + cm.getScrollInfo().clientHeight;
  1808           if (newBottom < cursorCoords.bottom) {
  1809              cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight;
  1810              cursor.line = Math.floor(cursor.line);
  1811              cm.setCursor(cursor);
  1812              cursorCoords = cm.charCoords(cursor, 'local');
  1813              cm.scrollTo(
  1814                  null, cursorCoords.bottom - cm.getScrollInfo().clientHeight);
  1815           } else {
  1816              // Cursor stays within bounds.  Just reposition the scroll window.
  1817              cm.scrollTo(null, newPos);
  1820       },
  1821       scrollToCursor: function(cm, actionArgs) {
  1822         var lineNum = cm.getCursor().line;
  1823         var charCoords = cm.charCoords(Pos(lineNum, 0), 'local');
  1824         var height = cm.getScrollInfo().clientHeight;
  1825         var y = charCoords.top;
  1826         var lineHeight = charCoords.bottom - y;
  1827         switch (actionArgs.position) {
  1828           case 'center': y = y - (height / 2) + lineHeight;
  1829             break;
  1830           case 'bottom': y = y - height + lineHeight*1.4;
  1831             break;
  1832           case 'top': y = y + lineHeight*0.4;
  1833             break;
  1835         cm.scrollTo(null, y);
  1836       },
  1837       replayMacro: function(cm, actionArgs, vim) {
  1838         var registerName = actionArgs.selectedCharacter;
  1839         var repeat = actionArgs.repeat;
  1840         var macroModeState = vimGlobalState.macroModeState;
  1841         if (registerName == '@') {
  1842           registerName = macroModeState.latestRegister;
  1844         while(repeat--){
  1845           executeMacroRegister(cm, vim, macroModeState, registerName);
  1847       },
  1848       enterMacroRecordMode: function(cm, actionArgs) {
  1849         var macroModeState = vimGlobalState.macroModeState;
  1850         var registerName = actionArgs.selectedCharacter;
  1851         macroModeState.enterMacroRecordMode(cm, registerName);
  1852       },
  1853       enterInsertMode: function(cm, actionArgs, vim) {
  1854         if (cm.getOption('readOnly')) { return; }
  1855         vim.insertMode = true;
  1856         vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
  1857         var insertAt = (actionArgs) ? actionArgs.insertAt : null;
  1858         if (insertAt == 'eol') {
  1859           var cursor = cm.getCursor();
  1860           cursor = Pos(cursor.line, lineLength(cm, cursor.line));
  1861           cm.setCursor(cursor);
  1862         } else if (insertAt == 'charAfter') {
  1863           cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
  1864         } else if (insertAt == 'firstNonBlank') {
  1865           cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
  1867         cm.setOption('keyMap', 'vim-insert');
  1868         cm.setOption('disableInput', false);
  1869         if (actionArgs && actionArgs.replace) {
  1870           // Handle Replace-mode as a special case of insert mode.
  1871           cm.toggleOverwrite(true);
  1872           cm.setOption('keyMap', 'vim-replace');
  1873           CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
  1874         } else {
  1875           cm.setOption('keyMap', 'vim-insert');
  1876           CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
  1878         if (!vimGlobalState.macroModeState.isPlaying) {
  1879           // Only record if not replaying.
  1880           cm.on('change', onChange);
  1881           cm.on('cursorActivity', onCursorActivity);
  1882           CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
  1884       },
  1885       toggleVisualMode: function(cm, actionArgs, vim) {
  1886         var repeat = actionArgs.repeat;
  1887         var curStart = cm.getCursor();
  1888         var curEnd;
  1889         // TODO: The repeat should actually select number of characters/lines
  1890         //     equal to the repeat times the size of the previous visual
  1891         //     operation.
  1892         if (!vim.visualMode) {
  1893           cm.on('mousedown', exitVisualMode);
  1894           vim.visualMode = true;
  1895           vim.visualLine = !!actionArgs.linewise;
  1896           if (vim.visualLine) {
  1897             curStart.ch = 0;
  1898             curEnd = clipCursorToContent(
  1899               cm, Pos(curStart.line + repeat - 1, lineLength(cm, curStart.line)),
  1900               true /** includeLineBreak */);
  1901           } else {
  1902             curEnd = clipCursorToContent(
  1903               cm, Pos(curStart.line, curStart.ch + repeat),
  1904               true /** includeLineBreak */);
  1906           // Make the initial selection.
  1907           if (!actionArgs.repeatIsExplicit && !vim.visualLine) {
  1908             // This is a strange case. Here the implicit repeat is 1. The
  1909             // following commands lets the cursor hover over the 1 character
  1910             // selection.
  1911             cm.setCursor(curEnd);
  1912             cm.setSelection(curEnd, curStart);
  1913           } else {
  1914             cm.setSelection(curStart, curEnd);
  1916           CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
  1917         } else {
  1918           curStart = cm.getCursor('anchor');
  1919           curEnd = cm.getCursor('head');
  1920           if (!vim.visualLine && actionArgs.linewise) {
  1921             // Shift-V pressed in characterwise visual mode. Switch to linewise
  1922             // visual mode instead of exiting visual mode.
  1923             vim.visualLine = true;
  1924             curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 :
  1925                 lineLength(cm, curStart.line);
  1926             curEnd.ch = cursorIsBefore(curStart, curEnd) ?
  1927                 lineLength(cm, curEnd.line) : 0;
  1928             cm.setSelection(curStart, curEnd);
  1929             CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"});
  1930           } else if (vim.visualLine && !actionArgs.linewise) {
  1931             // v pressed in linewise visual mode. Switch to characterwise visual
  1932             // mode instead of exiting visual mode.
  1933             vim.visualLine = false;
  1934             CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
  1935           } else {
  1936             exitVisualMode(cm);
  1939         updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart
  1940             : curEnd);
  1941         updateMark(cm, vim, '>', cursorIsBefore(curStart, curEnd) ? curEnd
  1942             : curStart);
  1943       },
  1944       reselectLastSelection: function(cm, _actionArgs, vim) {
  1945         if (vim.lastSelection) {
  1946           var lastSelection = vim.lastSelection;
  1947           cm.setSelection(lastSelection.curStart, lastSelection.curEnd);
  1948           if (lastSelection.visualLine) {
  1949             vim.visualMode = true;
  1950             vim.visualLine = true;
  1952           else {
  1953             vim.visualMode = true;
  1954             vim.visualLine = false;
  1956           CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
  1958       },
  1959       joinLines: function(cm, actionArgs, vim) {
  1960         var curStart, curEnd;
  1961         if (vim.visualMode) {
  1962           curStart = cm.getCursor('anchor');
  1963           curEnd = cm.getCursor('head');
  1964           curEnd.ch = lineLength(cm, curEnd.line) - 1;
  1965         } else {
  1966           // Repeat is the number of lines to join. Minimum 2 lines.
  1967           var repeat = Math.max(actionArgs.repeat, 2);
  1968           curStart = cm.getCursor();
  1969           curEnd = clipCursorToContent(cm, Pos(curStart.line + repeat - 1,
  1970                                                Infinity));
  1972         var finalCh = 0;
  1973         cm.operation(function() {
  1974           for (var i = curStart.line; i < curEnd.line; i++) {
  1975             finalCh = lineLength(cm, curStart.line);
  1976             var tmp = Pos(curStart.line + 1,
  1977                           lineLength(cm, curStart.line + 1));
  1978             var text = cm.getRange(curStart, tmp);
  1979             text = text.replace(/\n\s*/g, ' ');
  1980             cm.replaceRange(text, curStart, tmp);
  1982           var curFinalPos = Pos(curStart.line, finalCh);
  1983           cm.setCursor(curFinalPos);
  1984         });
  1985       },
  1986       newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
  1987         vim.insertMode = true;
  1988         var insertAt = copyCursor(cm.getCursor());
  1989         if (insertAt.line === cm.firstLine() && !actionArgs.after) {
  1990           // Special case for inserting newline before start of document.
  1991           cm.replaceRange('\n', Pos(cm.firstLine(), 0));
  1992           cm.setCursor(cm.firstLine(), 0);
  1993         } else {
  1994           insertAt.line = (actionArgs.after) ? insertAt.line :
  1995               insertAt.line - 1;
  1996           insertAt.ch = lineLength(cm, insertAt.line);
  1997           cm.setCursor(insertAt);
  1998           var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||
  1999               CodeMirror.commands.newlineAndIndent;
  2000           newlineFn(cm);
  2002         this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);
  2003       },
  2004       paste: function(cm, actionArgs) {
  2005         var cur = copyCursor(cm.getCursor());
  2006         var register = vimGlobalState.registerController.getRegister(
  2007             actionArgs.registerName);
  2008         var text = register.toString();
  2009         if (!text) {
  2010           return;
  2012         if (actionArgs.repeat > 1) {
  2013           var text = Array(actionArgs.repeat + 1).join(text);
  2015         var linewise = register.linewise;
  2016         if (linewise) {
  2017           if (actionArgs.after) {
  2018             // Move the newline at the end to the start instead, and paste just
  2019             // before the newline character of the line we are on right now.
  2020             text = '\n' + text.slice(0, text.length - 1);
  2021             cur.ch = lineLength(cm, cur.line);
  2022           } else {
  2023             cur.ch = 0;
  2025         } else {
  2026           cur.ch += actionArgs.after ? 1 : 0;
  2028         cm.replaceRange(text, cur);
  2029         // Now fine tune the cursor to where we want it.
  2030         var curPosFinal;
  2031         var idx;
  2032         if (linewise && actionArgs.after) {
  2033           curPosFinal = Pos(
  2034             cur.line + 1,
  2035             findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)));
  2036         } else if (linewise && !actionArgs.after) {
  2037           curPosFinal = Pos(
  2038             cur.line,
  2039             findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
  2040         } else if (!linewise && actionArgs.after) {
  2041           idx = cm.indexFromPos(cur);
  2042           curPosFinal = cm.posFromIndex(idx + text.length - 1);
  2043         } else {
  2044           idx = cm.indexFromPos(cur);
  2045           curPosFinal = cm.posFromIndex(idx + text.length);
  2047         cm.setCursor(curPosFinal);
  2048       },
  2049       undo: function(cm, actionArgs) {
  2050         cm.operation(function() {
  2051           repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
  2052           cm.setCursor(cm.getCursor('anchor'));
  2053         });
  2054       },
  2055       redo: function(cm, actionArgs) {
  2056         repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
  2057       },
  2058       setRegister: function(_cm, actionArgs, vim) {
  2059         vim.inputState.registerName = actionArgs.selectedCharacter;
  2060       },
  2061       setMark: function(cm, actionArgs, vim) {
  2062         var markName = actionArgs.selectedCharacter;
  2063         updateMark(cm, vim, markName, cm.getCursor());
  2064       },
  2065       replace: function(cm, actionArgs, vim) {
  2066         var replaceWith = actionArgs.selectedCharacter;
  2067         var curStart = cm.getCursor();
  2068         var replaceTo;
  2069         var curEnd;
  2070         if (vim.visualMode){
  2071           curStart=cm.getCursor('start');
  2072           curEnd=cm.getCursor('end');
  2073           // workaround to catch the character under the cursor
  2074           //  existing workaround doesn't cover actions
  2075           curEnd=cm.clipPos(Pos(curEnd.line, curEnd.ch+1));
  2076         }else{
  2077           var line = cm.getLine(curStart.line);
  2078           replaceTo = curStart.ch + actionArgs.repeat;
  2079           if (replaceTo > line.length) {
  2080             replaceTo=line.length;
  2082           curEnd = Pos(curStart.line, replaceTo);
  2084         if (replaceWith=='\n'){
  2085           if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
  2086           // special case, where vim help says to replace by just one line-break
  2087           (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
  2088         }else {
  2089           var replaceWithStr=cm.getRange(curStart, curEnd);
  2090           //replace all characters in range by selected, but keep linebreaks
  2091           replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith);
  2092           cm.replaceRange(replaceWithStr, curStart, curEnd);
  2093           if (vim.visualMode){
  2094             cm.setCursor(curStart);
  2095             exitVisualMode(cm);
  2096           }else{
  2097             cm.setCursor(offsetCursor(curEnd, 0, -1));
  2100       },
  2101       incrementNumberToken: function(cm, actionArgs) {
  2102         var cur = cm.getCursor();
  2103         var lineStr = cm.getLine(cur.line);
  2104         var re = /-?\d+/g;
  2105         var match;
  2106         var start;
  2107         var end;
  2108         var numberStr;
  2109         var token;
  2110         while ((match = re.exec(lineStr)) !== null) {
  2111           token = match[0];
  2112           start = match.index;
  2113           end = start + token.length;
  2114           if (cur.ch < end)break;
  2116         if (!actionArgs.backtrack && (end <= cur.ch))return;
  2117         if (token) {
  2118           var increment = actionArgs.increase ? 1 : -1;
  2119           var number = parseInt(token) + (increment * actionArgs.repeat);
  2120           var from = Pos(cur.line, start);
  2121           var to = Pos(cur.line, end);
  2122           numberStr = number.toString();
  2123           cm.replaceRange(numberStr, from, to);
  2124         } else {
  2125           return;
  2127         cm.setCursor(Pos(cur.line, start + numberStr.length - 1));
  2128       },
  2129       repeatLastEdit: function(cm, actionArgs, vim) {
  2130         var lastEditInputState = vim.lastEditInputState;
  2131         if (!lastEditInputState) { return; }
  2132         var repeat = actionArgs.repeat;
  2133         if (repeat && actionArgs.repeatIsExplicit) {
  2134           vim.lastEditInputState.repeatOverride = repeat;
  2135         } else {
  2136           repeat = vim.lastEditInputState.repeatOverride || repeat;
  2138         repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
  2140     };
  2142     /*
  2143      * Below are miscellaneous utility functions used by vim.js
  2144      */
  2146     /**
  2147      * Clips cursor to ensure that line is within the buffer's range
  2148      * If includeLineBreak is true, then allow cur.ch == lineLength.
  2149      */
  2150     function clipCursorToContent(cm, cur, includeLineBreak) {
  2151       var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );
  2152       var maxCh = lineLength(cm, line) - 1;
  2153       maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
  2154       var ch = Math.min(Math.max(0, cur.ch), maxCh);
  2155       return Pos(line, ch);
  2157     function copyArgs(args) {
  2158       var ret = {};
  2159       for (var prop in args) {
  2160         if (args.hasOwnProperty(prop)) {
  2161           ret[prop] = args[prop];
  2164       return ret;
  2166     function offsetCursor(cur, offsetLine, offsetCh) {
  2167       return Pos(cur.line + offsetLine, cur.ch + offsetCh);
  2169     function matchKeysPartial(pressed, mapped) {
  2170       for (var i = 0; i < pressed.length; i++) {
  2171         // 'character' means any character. For mark, register commads, etc.
  2172         if (pressed[i] != mapped[i] && mapped[i] != 'character') {
  2173           return false;
  2176       return true;
  2178     function repeatFn(cm, fn, repeat) {
  2179       return function() {
  2180         for (var i = 0; i < repeat; i++) {
  2181           fn(cm);
  2183       };
  2185     function copyCursor(cur) {
  2186       return Pos(cur.line, cur.ch);
  2188     function cursorEqual(cur1, cur2) {
  2189       return cur1.ch == cur2.ch && cur1.line == cur2.line;
  2191     function cursorIsBefore(cur1, cur2) {
  2192       if (cur1.line < cur2.line) {
  2193         return true;
  2195       if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
  2196         return true;
  2198       return false;
  2200     function cusrorIsBetween(cur1, cur2, cur3) {
  2201       // returns true if cur2 is between cur1 and cur3.
  2202       var cur1before2 = cursorIsBefore(cur1, cur2);
  2203       var cur2before3 = cursorIsBefore(cur2, cur3);
  2204       return cur1before2 && cur2before3;
  2206     function lineLength(cm, lineNum) {
  2207       return cm.getLine(lineNum).length;
  2209     function reverse(s){
  2210       return s.split('').reverse().join('');
  2212     function trim(s) {
  2213       if (s.trim) {
  2214         return s.trim();
  2216       return s.replace(/^\s+|\s+$/g, '');
  2218     function escapeRegex(s) {
  2219       return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
  2222     function exitVisualMode(cm) {
  2223       cm.off('mousedown', exitVisualMode);
  2224       var vim = cm.state.vim;
  2225       // can't use selection state here because yank has already reset its cursor
  2226       vim.lastSelection = {'curStart': vim.marks['<'].find(),
  2227         'curEnd': vim.marks['>'].find(), 'visualMode': vim.visualMode,
  2228         'visualLine': vim.visualLine};
  2229       vim.visualMode = false;
  2230       vim.visualLine = false;
  2231       var selectionStart = cm.getCursor('anchor');
  2232       var selectionEnd = cm.getCursor('head');
  2233       if (!cursorEqual(selectionStart, selectionEnd)) {
  2234         // Clear the selection and set the cursor only if the selection has not
  2235         // already been cleared. Otherwise we risk moving the cursor somewhere
  2236         // it's not supposed to be.
  2237         cm.setCursor(clipCursorToContent(cm, selectionEnd));
  2239       CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
  2242     // Remove any trailing newlines from the selection. For
  2243     // example, with the caret at the start of the last word on the line,
  2244     // 'dw' should word, but not the newline, while 'w' should advance the
  2245     // caret to the first character of the next line.
  2246     function clipToLine(cm, curStart, curEnd) {
  2247       var selection = cm.getRange(curStart, curEnd);
  2248       // Only clip if the selection ends with trailing newline + whitespace
  2249       if (/\n\s*$/.test(selection)) {
  2250         var lines = selection.split('\n');
  2251         // We know this is all whitepsace.
  2252         lines.pop();
  2254         // Cases:
  2255         // 1. Last word is an empty line - do not clip the trailing '\n'
  2256         // 2. Last word is not an empty line - clip the trailing '\n'
  2257         var line;
  2258         // Find the line containing the last word, and clip all whitespace up
  2259         // to it.
  2260         for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
  2261           curEnd.line--;
  2262           curEnd.ch = 0;
  2264         // If the last word is not an empty line, clip an additional newline
  2265         if (line) {
  2266           curEnd.line--;
  2267           curEnd.ch = lineLength(cm, curEnd.line);
  2268         } else {
  2269           curEnd.ch = 0;
  2274     // Expand the selection to line ends.
  2275     function expandSelectionToLine(_cm, curStart, curEnd) {
  2276       curStart.ch = 0;
  2277       curEnd.ch = 0;
  2278       curEnd.line++;
  2281     function findFirstNonWhiteSpaceCharacter(text) {
  2282       if (!text) {
  2283         return 0;
  2285       var firstNonWS = text.search(/\S/);
  2286       return firstNonWS == -1 ? text.length : firstNonWS;
  2289     function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
  2290       var cur = cm.getCursor();
  2291       var line = cm.getLine(cur.line);
  2292       var idx = cur.ch;
  2294       // Seek to first word or non-whitespace character, depending on if
  2295       // noSymbol is true.
  2296       var textAfterIdx = line.substring(idx);
  2297       var firstMatchedChar;
  2298       if (noSymbol) {
  2299         firstMatchedChar = textAfterIdx.search(/\w/);
  2300       } else {
  2301         firstMatchedChar = textAfterIdx.search(/\S/);
  2303       if (firstMatchedChar == -1) {
  2304         return null;
  2306       idx += firstMatchedChar;
  2307       textAfterIdx = line.substring(idx);
  2308       var textBeforeIdx = line.substring(0, idx);
  2310       var matchRegex;
  2311       // Greedy matchers for the "word" we are trying to expand.
  2312       if (bigWord) {
  2313         matchRegex = /^\S+/;
  2314       } else {
  2315         if ((/\w/).test(line.charAt(idx))) {
  2316           matchRegex = /^\w+/;
  2317         } else {
  2318           matchRegex = /^[^\w\s]+/;
  2322       var wordAfterRegex = matchRegex.exec(textAfterIdx);
  2323       var wordStart = idx;
  2324       var wordEnd = idx + wordAfterRegex[0].length;
  2325       // TODO: Find a better way to do this. It will be slow on very long lines.
  2326       var revTextBeforeIdx = reverse(textBeforeIdx);
  2327       var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx);
  2328       if (wordBeforeRegex) {
  2329         wordStart -= wordBeforeRegex[0].length;
  2332       if (inclusive) {
  2333         // If present, trim all whitespace after word.
  2334         // Otherwise, trim all whitespace before word.
  2335         var textAfterWordEnd = line.substring(wordEnd);
  2336         var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length;
  2337         if (whitespacesAfterWord > 0) {
  2338           wordEnd += whitespacesAfterWord;
  2339         } else {
  2340           var revTrim = revTextBeforeIdx.length - wordStart;
  2341           var textBeforeWordStart = revTextBeforeIdx.substring(revTrim);
  2342           var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length;
  2343           wordStart -= whitespacesBeforeWord;
  2347       return { start: Pos(cur.line, wordStart),
  2348                end: Pos(cur.line, wordEnd) };
  2351     function recordJumpPosition(cm, oldCur, newCur) {
  2352       if (!cursorEqual(oldCur, newCur)) {
  2353         vimGlobalState.jumpList.add(cm, oldCur, newCur);
  2357     function recordLastCharacterSearch(increment, args) {
  2358         vimGlobalState.lastChararacterSearch.increment = increment;
  2359         vimGlobalState.lastChararacterSearch.forward = args.forward;
  2360         vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter;
  2363     var symbolToMode = {
  2364         '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',
  2365         '[': 'section', ']': 'section',
  2366         '*': 'comment', '/': 'comment',
  2367         'm': 'method', 'M': 'method',
  2368         '#': 'preprocess'
  2369     };
  2370     var findSymbolModes = {
  2371       bracket: {
  2372         isComplete: function(state) {
  2373           if (state.nextCh === state.symb) {
  2374             state.depth++;
  2375             if (state.depth >= 1)return true;
  2376           } else if (state.nextCh === state.reverseSymb) {
  2377             state.depth--;
  2379           return false;
  2381       },
  2382       section: {
  2383         init: function(state) {
  2384           state.curMoveThrough = true;
  2385           state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';
  2386         },
  2387         isComplete: function(state) {
  2388           return state.index === 0 && state.nextCh === state.symb;
  2390       },
  2391       comment: {
  2392         isComplete: function(state) {
  2393           var found = state.lastCh === '*' && state.nextCh === '/';
  2394           state.lastCh = state.nextCh;
  2395           return found;
  2397       },
  2398       // TODO: The original Vim implementation only operates on level 1 and 2.
  2399       // The current implementation doesn't check for code block level and
  2400       // therefore it operates on any levels.
  2401       method: {
  2402         init: function(state) {
  2403           state.symb = (state.symb === 'm' ? '{' : '}');
  2404           state.reverseSymb = state.symb === '{' ? '}' : '{';
  2405         },
  2406         isComplete: function(state) {
  2407           if (state.nextCh === state.symb)return true;
  2408           return false;
  2410       },
  2411       preprocess: {
  2412         init: function(state) {
  2413           state.index = 0;
  2414         },
  2415         isComplete: function(state) {
  2416           if (state.nextCh === '#') {
  2417             var token = state.lineText.match(/#(\w+)/)[1];
  2418             if (token === 'endif') {
  2419               if (state.forward && state.depth === 0) {
  2420                 return true;
  2422               state.depth++;
  2423             } else if (token === 'if') {
  2424               if (!state.forward && state.depth === 0) {
  2425                 return true;
  2427               state.depth--;
  2429             if (token === 'else' && state.depth === 0)return true;
  2431           return false;
  2434     };
  2435     function findSymbol(cm, repeat, forward, symb) {
  2436       var cur = copyCursor(cm.getCursor());
  2437       var increment = forward ? 1 : -1;
  2438       var endLine = forward ? cm.lineCount() : -1;
  2439       var curCh = cur.ch;
  2440       var line = cur.line;
  2441       var lineText = cm.getLine(line);
  2442       var state = {
  2443         lineText: lineText,
  2444         nextCh: lineText.charAt(curCh),
  2445         lastCh: null,
  2446         index: curCh,
  2447         symb: symb,
  2448         reverseSymb: (forward ?  { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
  2449         forward: forward,
  2450         depth: 0,
  2451         curMoveThrough: false
  2452       };
  2453       var mode = symbolToMode[symb];
  2454       if (!mode)return cur;
  2455       var init = findSymbolModes[mode].init;
  2456       var isComplete = findSymbolModes[mode].isComplete;
  2457       if (init) { init(state); }
  2458       while (line !== endLine && repeat) {
  2459         state.index += increment;
  2460         state.nextCh = state.lineText.charAt(state.index);
  2461         if (!state.nextCh) {
  2462           line += increment;
  2463           state.lineText = cm.getLine(line) || '';
  2464           if (increment > 0) {
  2465             state.index = 0;
  2466           } else {
  2467             var lineLen = state.lineText.length;
  2468             state.index = (lineLen > 0) ? (lineLen-1) : 0;
  2470           state.nextCh = state.lineText.charAt(state.index);
  2472         if (isComplete(state)) {
  2473           cur.line = line;
  2474           cur.ch = state.index;
  2475           repeat--;
  2478       if (state.nextCh || state.curMoveThrough) {
  2479         return Pos(line, state.index);
  2481       return cur;
  2484     /*
  2485      * Returns the boundaries of the next word. If the cursor in the middle of
  2486      * the word, then returns the boundaries of the current word, starting at
  2487      * the cursor. If the cursor is at the start/end of a word, and we are going
  2488      * forward/backward, respectively, find the boundaries of the next word.
  2490      * @param {CodeMirror} cm CodeMirror object.
  2491      * @param {Cursor} cur The cursor position.
  2492      * @param {boolean} forward True to search forward. False to search
  2493      *     backward.
  2494      * @param {boolean} bigWord True if punctuation count as part of the word.
  2495      *     False if only [a-zA-Z0-9] characters count as part of the word.
  2496      * @param {boolean} emptyLineIsWord True if empty lines should be treated
  2497      *     as words.
  2498      * @return {Object{from:number, to:number, line: number}} The boundaries of
  2499      *     the word, or null if there are no more words.
  2500      */
  2501     function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {
  2502       var lineNum = cur.line;
  2503       var pos = cur.ch;
  2504       var line = cm.getLine(lineNum);
  2505       var dir = forward ? 1 : -1;
  2506       var regexps = bigWord ? bigWordRegexp : wordRegexp;
  2508       if (emptyLineIsWord && line == '') {
  2509         lineNum += dir;
  2510         line = cm.getLine(lineNum);
  2511         if (!isLine(cm, lineNum)) {
  2512           return null;
  2514         pos = (forward) ? 0 : line.length;
  2517       while (true) {
  2518         if (emptyLineIsWord && line == '') {
  2519           return { from: 0, to: 0, line: lineNum };
  2521         var stop = (dir > 0) ? line.length : -1;
  2522         var wordStart = stop, wordEnd = stop;
  2523         // Find bounds of next word.
  2524         while (pos != stop) {
  2525           var foundWord = false;
  2526           for (var i = 0; i < regexps.length && !foundWord; ++i) {
  2527             if (regexps[i].test(line.charAt(pos))) {
  2528               wordStart = pos;
  2529               // Advance to end of word.
  2530               while (pos != stop && regexps[i].test(line.charAt(pos))) {
  2531                 pos += dir;
  2533               wordEnd = pos;
  2534               foundWord = wordStart != wordEnd;
  2535               if (wordStart == cur.ch && lineNum == cur.line &&
  2536                   wordEnd == wordStart + dir) {
  2537                 // We started at the end of a word. Find the next one.
  2538                 continue;
  2539               } else {
  2540                 return {
  2541                   from: Math.min(wordStart, wordEnd + 1),
  2542                   to: Math.max(wordStart, wordEnd),
  2543                   line: lineNum };
  2547           if (!foundWord) {
  2548             pos += dir;
  2551         // Advance to next/prev line.
  2552         lineNum += dir;
  2553         if (!isLine(cm, lineNum)) {
  2554           return null;
  2556         line = cm.getLine(lineNum);
  2557         pos = (dir > 0) ? 0 : line.length;
  2559       // Should never get here.
  2560       throw new Error('The impossible happened.');
  2563     /**
  2564      * @param {CodeMirror} cm CodeMirror object.
  2565      * @param {int} repeat Number of words to move past.
  2566      * @param {boolean} forward True to search forward. False to search
  2567      *     backward.
  2568      * @param {boolean} wordEnd True to move to end of word. False to move to
  2569      *     beginning of word.
  2570      * @param {boolean} bigWord True if punctuation count as part of the word.
  2571      *     False if only alphabet characters count as part of the word.
  2572      * @return {Cursor} The position the cursor should move to.
  2573      */
  2574     function moveToWord(cm, repeat, forward, wordEnd, bigWord) {
  2575       var cur = cm.getCursor();
  2576       var curStart = copyCursor(cur);
  2577       var words = [];
  2578       if (forward && !wordEnd || !forward && wordEnd) {
  2579         repeat++;
  2581       // For 'e', empty lines are not considered words, go figure.
  2582       var emptyLineIsWord = !(forward && wordEnd);
  2583       for (var i = 0; i < repeat; i++) {
  2584         var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);
  2585         if (!word) {
  2586           var eodCh = lineLength(cm, cm.lastLine());
  2587           words.push(forward
  2588               ? {line: cm.lastLine(), from: eodCh, to: eodCh}
  2589               : {line: 0, from: 0, to: 0});
  2590           break;
  2592         words.push(word);
  2593         cur = Pos(word.line, forward ? (word.to - 1) : word.from);
  2595       var shortCircuit = words.length != repeat;
  2596       var firstWord = words[0];
  2597       var lastWord = words.pop();
  2598       if (forward && !wordEnd) {
  2599         // w
  2600         if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {
  2601           // We did not start in the middle of a word. Discard the extra word at the end.
  2602           lastWord = words.pop();
  2604         return Pos(lastWord.line, lastWord.from);
  2605       } else if (forward && wordEnd) {
  2606         return Pos(lastWord.line, lastWord.to - 1);
  2607       } else if (!forward && wordEnd) {
  2608         // ge
  2609         if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {
  2610           // We did not start in the middle of a word. Discard the extra word at the end.
  2611           lastWord = words.pop();
  2613         return Pos(lastWord.line, lastWord.to);
  2614       } else {
  2615         // b
  2616         return Pos(lastWord.line, lastWord.from);
  2620     function moveToCharacter(cm, repeat, forward, character) {
  2621       var cur = cm.getCursor();
  2622       var start = cur.ch;
  2623       var idx;
  2624       for (var i = 0; i < repeat; i ++) {
  2625         var line = cm.getLine(cur.line);
  2626         idx = charIdxInLine(start, line, character, forward, true);
  2627         if (idx == -1) {
  2628           return null;
  2630         start = idx;
  2632       return Pos(cm.getCursor().line, idx);
  2635     function moveToColumn(cm, repeat) {
  2636       // repeat is always >= 1, so repeat - 1 always corresponds
  2637       // to the column we want to go to.
  2638       var line = cm.getCursor().line;
  2639       return clipCursorToContent(cm, Pos(line, repeat - 1));
  2642     function updateMark(cm, vim, markName, pos) {
  2643       if (!inArray(markName, validMarks)) {
  2644         return;
  2646       if (vim.marks[markName]) {
  2647         vim.marks[markName].clear();
  2649       vim.marks[markName] = cm.setBookmark(pos);
  2652     function charIdxInLine(start, line, character, forward, includeChar) {
  2653       // Search for char in line.
  2654       // motion_options: {forward, includeChar}
  2655       // If includeChar = true, include it too.
  2656       // If forward = true, search forward, else search backwards.
  2657       // If char is not found on this line, do nothing
  2658       var idx;
  2659       if (forward) {
  2660         idx = line.indexOf(character, start + 1);
  2661         if (idx != -1 && !includeChar) {
  2662           idx -= 1;
  2664       } else {
  2665         idx = line.lastIndexOf(character, start - 1);
  2666         if (idx != -1 && !includeChar) {
  2667           idx += 1;
  2670       return idx;
  2673     function getContextLevel(ctx) {
  2674       return (ctx === 'string' || ctx === 'comment') ? 1 : 0;
  2677     function findMatchedSymbol(cm, cur, symb) {
  2678       var line = cur.line;
  2679       var ch = cur.ch;
  2680       symb = symb ? symb : cm.getLine(line).charAt(ch);
  2682       var symbContext = cm.getTokenAt(Pos(line, ch + 1)).type;
  2683       var symbCtxLevel = getContextLevel(symbContext);
  2685       var reverseSymb = ({
  2686         '(': ')', ')': '(',
  2687         '[': ']', ']': '[',
  2688         '{': '}', '}': '{'})[symb];
  2690       // Couldn't find a matching symbol, abort
  2691       if (!reverseSymb) {
  2692         return cur;
  2695       // set our increment to move forward (+1) or backwards (-1)
  2696       // depending on which bracket we're matching
  2697       var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1;
  2698       var endLine = increment === 1 ? cm.lineCount() : -1;
  2699       var depth = 1, nextCh = symb, index = ch, lineText = cm.getLine(line);
  2700       // Simple search for closing paren--just count openings and closings till
  2701       // we find our match
  2702       // TODO: use info from CodeMirror to ignore closing brackets in comments
  2703       // and quotes, etc.
  2704       while (line !== endLine && depth > 0) {
  2705         index += increment;
  2706         nextCh = lineText.charAt(index);
  2707         if (!nextCh) {
  2708           line += increment;
  2709           lineText = cm.getLine(line) || '';
  2710           if (increment > 0) {
  2711             index = 0;
  2712           } else {
  2713             var lineLen = lineText.length;
  2714             index = (lineLen > 0) ? (lineLen-1) : 0;
  2716           nextCh = lineText.charAt(index);
  2718         var revSymbContext = cm.getTokenAt(Pos(line, index + 1)).type;
  2719         var revSymbCtxLevel = getContextLevel(revSymbContext);
  2720         if (symbCtxLevel >= revSymbCtxLevel) {
  2721           if (nextCh === symb) {
  2722             depth++;
  2723           } else if (nextCh === reverseSymb) {
  2724             depth--;
  2729       if (nextCh) {
  2730         return Pos(line, index);
  2732       return cur;
  2735     // TODO: perhaps this finagling of start and end positions belonds
  2736     // in codmirror/replaceRange?
  2737     function selectCompanionObject(cm, revSymb, inclusive) {
  2738       var cur = copyCursor(cm.getCursor());
  2739       var end = findMatchedSymbol(cm, cur, revSymb);
  2740       var start = findMatchedSymbol(cm, end);
  2742       if ((start.line == end.line && start.ch > end.ch)
  2743           || (start.line > end.line)) {
  2744         var tmp = start;
  2745         start = end;
  2746         end = tmp;
  2749       if (inclusive) {
  2750         end.ch += 1;
  2751       } else {
  2752         start.ch += 1;
  2755       return { start: start, end: end };
  2758     // Takes in a symbol and a cursor and tries to simulate text objects that
  2759     // have identical opening and closing symbols
  2760     // TODO support across multiple lines
  2761     function findBeginningAndEnd(cm, symb, inclusive) {
  2762       var cur = copyCursor(cm.getCursor());
  2763       var line = cm.getLine(cur.line);
  2764       var chars = line.split('');
  2765       var start, end, i, len;
  2766       var firstIndex = chars.indexOf(symb);
  2768       // the decision tree is to always look backwards for the beginning first,
  2769       // but if the cursor is in front of the first instance of the symb,
  2770       // then move the cursor forward
  2771       if (cur.ch < firstIndex) {
  2772         cur.ch = firstIndex;
  2773         // Why is this line even here???
  2774         // cm.setCursor(cur.line, firstIndex+1);
  2776       // otherwise if the cursor is currently on the closing symbol
  2777       else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
  2778         end = cur.ch; // assign end to the current cursor
  2779         --cur.ch; // make sure to look backwards
  2782       // if we're currently on the symbol, we've got a start
  2783       if (chars[cur.ch] == symb && !end) {
  2784         start = cur.ch + 1; // assign start to ahead of the cursor
  2785       } else {
  2786         // go backwards to find the start
  2787         for (i = cur.ch; i > -1 && !start; i--) {
  2788           if (chars[i] == symb) {
  2789             start = i + 1;
  2794       // look forwards for the end symbol
  2795       if (start && !end) {
  2796         for (i = start, len = chars.length; i < len && !end; i++) {
  2797           if (chars[i] == symb) {
  2798             end = i;
  2803       // nothing found
  2804       if (!start || !end) {
  2805         return { start: cur, end: cur };
  2808       // include the symbols
  2809       if (inclusive) {
  2810         --start; ++end;
  2813       return {
  2814         start: Pos(cur.line, start),
  2815         end: Pos(cur.line, end)
  2816       };
  2819     // Search functions
  2820     defineOption('pcre', true, 'boolean');
  2821     function SearchState() {}
  2822     SearchState.prototype = {
  2823       getQuery: function() {
  2824         return vimGlobalState.query;
  2825       },
  2826       setQuery: function(query) {
  2827         vimGlobalState.query = query;
  2828       },
  2829       getOverlay: function() {
  2830         return this.searchOverlay;
  2831       },
  2832       setOverlay: function(overlay) {
  2833         this.searchOverlay = overlay;
  2834       },
  2835       isReversed: function() {
  2836         return vimGlobalState.isReversed;
  2837       },
  2838       setReversed: function(reversed) {
  2839         vimGlobalState.isReversed = reversed;
  2841     };
  2842     function getSearchState(cm) {
  2843       var vim = cm.state.vim;
  2844       return vim.searchState_ || (vim.searchState_ = new SearchState());
  2846     function dialog(cm, template, shortText, onClose, options) {
  2847       if (cm.openDialog) {
  2848         cm.openDialog(template, onClose, { bottom: true, value: options.value,
  2849             onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp });
  2851       else {
  2852         onClose(prompt(shortText, ''));
  2856     function findUnescapedSlashes(str) {
  2857       var escapeNextChar = false;
  2858       var slashes = [];
  2859       for (var i = 0; i < str.length; i++) {
  2860         var c = str.charAt(i);
  2861         if (!escapeNextChar && c == '/') {
  2862           slashes.push(i);
  2864         escapeNextChar = !escapeNextChar && (c == '\\');
  2866       return slashes;
  2869     // Translates a search string from ex (vim) syntax into javascript form.
  2870     function translateRegex(str) {
  2871       // When these match, add a '\' if unescaped or remove one if escaped.
  2872       var specials = ['|', '(', ')', '{'];
  2873       // Remove, but never add, a '\' for these.
  2874       var unescape = ['}'];
  2875       var escapeNextChar = false;
  2876       var out = [];
  2877       for (var i = -1; i < str.length; i++) {
  2878         var c = str.charAt(i) || '';
  2879         var n = str.charAt(i+1) || '';
  2880         var specialComesNext = (specials.indexOf(n) != -1);
  2881         if (escapeNextChar) {
  2882           if (c !== '\\' || !specialComesNext) {
  2883             out.push(c);
  2885           escapeNextChar = false;
  2886         } else {
  2887           if (c === '\\') {
  2888             escapeNextChar = true;
  2889             // Treat the unescape list as special for removing, but not adding '\'.
  2890             if (unescape.indexOf(n) != -1) {
  2891               specialComesNext = true;
  2893             // Not passing this test means removing a '\'.
  2894             if (!specialComesNext || n === '\\') {
  2895               out.push(c);
  2897           } else {
  2898             out.push(c);
  2899             if (specialComesNext && n !== '\\') {
  2900               out.push('\\');
  2905       return out.join('');
  2908     // Translates the replace part of a search and replace from ex (vim) syntax into
  2909     // javascript form.  Similar to translateRegex, but additionally fixes back references
  2910     // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'.
  2911     function translateRegexReplace(str) {
  2912       var escapeNextChar = false;
  2913       var out = [];
  2914       for (var i = -1; i < str.length; i++) {
  2915         var c = str.charAt(i) || '';
  2916         var n = str.charAt(i+1) || '';
  2917         if (escapeNextChar) {
  2918           // At any point in the loop, escapeNextChar is true if the previous
  2919           // character was a '\' and was not escaped.
  2920           out.push(c);
  2921           escapeNextChar = false;
  2922         } else {
  2923           if (c === '\\') {
  2924             escapeNextChar = true;
  2925             if ((isNumber(n) || n === '$')) {
  2926               out.push('$');
  2927             } else if (n !== '/' && n !== '\\') {
  2928               out.push('\\');
  2930           } else {
  2931             if (c === '$') {
  2932               out.push('$');
  2934             out.push(c);
  2935             if (n === '/') {
  2936               out.push('\\');
  2941       return out.join('');
  2944     // Unescape \ and / in the replace part, for PCRE mode.
  2945     function unescapeRegexReplace(str) {
  2946       var stream = new CodeMirror.StringStream(str);
  2947       var output = [];
  2948       while (!stream.eol()) {
  2949         // Search for \.
  2950         while (stream.peek() && stream.peek() != '\\') {
  2951           output.push(stream.next());
  2953         if (stream.match('\\/', true)) {
  2954           // \/ => /
  2955           output.push('/');
  2956         } else if (stream.match('\\\\', true)) {
  2957           // \\ => \
  2958           output.push('\\');
  2959         } else {
  2960           // Don't change anything
  2961           output.push(stream.next());
  2964       return output.join('');
  2967     /**
  2968      * Extract the regular expression from the query and return a Regexp object.
  2969      * Returns null if the query is blank.
  2970      * If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
  2971      * If smartCase is passed in, and the query contains upper case letters,
  2972      *   then ignoreCase is overridden, and the 'i' flag will not be set.
  2973      * If the query contains the /i in the flag part of the regular expression,
  2974      *   then both ignoreCase and smartCase are ignored, and 'i' will be passed
  2975      *   through to the Regex object.
  2976      */
  2977     function parseQuery(query, ignoreCase, smartCase) {
  2978       // Check if the query is already a regex.
  2979       if (query instanceof RegExp) { return query; }
  2980       // First try to extract regex + flags from the input. If no flags found,
  2981       // extract just the regex. IE does not accept flags directly defined in
  2982       // the regex string in the form /regex/flags
  2983       var slashes = findUnescapedSlashes(query);
  2984       var regexPart;
  2985       var forceIgnoreCase;
  2986       if (!slashes.length) {
  2987         // Query looks like 'regexp'
  2988         regexPart = query;
  2989       } else {
  2990         // Query looks like 'regexp/...'
  2991         regexPart = query.substring(0, slashes[0]);
  2992         var flagsPart = query.substring(slashes[0]);
  2993         forceIgnoreCase = (flagsPart.indexOf('i') != -1);
  2995       if (!regexPart) {
  2996         return null;
  2998       if (!getOption('pcre')) {
  2999         regexPart = translateRegex(regexPart);
  3001       if (smartCase) {
  3002         ignoreCase = (/^[^A-Z]*$/).test(regexPart);
  3004       var regexp = new RegExp(regexPart,
  3005           (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
  3006       return regexp;
  3008     function showConfirm(cm, text) {
  3009       if (cm.openNotification) {
  3010         cm.openNotification('<span style="color: red">' + text + '</span>',
  3011                             {bottom: true, duration: 5000});
  3012       } else {
  3013         alert(text);
  3016     function makePrompt(prefix, desc) {
  3017       var raw = '';
  3018       if (prefix) {
  3019         raw += '<span style="font-family: monospace">' + prefix + '</span>';
  3021       raw += '<input type="text"/> ' +
  3022           '<span style="color: #888">';
  3023       if (desc) {
  3024         raw += '<span style="color: #888">';
  3025         raw += desc;
  3026         raw += '</span>';
  3028       return raw;
  3030     var searchPromptDesc = '(Javascript regexp)';
  3031     function showPrompt(cm, options) {
  3032       var shortText = (options.prefix || '') + ' ' + (options.desc || '');
  3033       var prompt = makePrompt(options.prefix, options.desc);
  3034       dialog(cm, prompt, shortText, options.onClose, options);
  3036     function regexEqual(r1, r2) {
  3037       if (r1 instanceof RegExp && r2 instanceof RegExp) {
  3038           var props = ['global', 'multiline', 'ignoreCase', 'source'];
  3039           for (var i = 0; i < props.length; i++) {
  3040               var prop = props[i];
  3041               if (r1[prop] !== r2[prop]) {
  3042                   return false;
  3045           return true;
  3047       return false;
  3049     // Returns true if the query is valid.
  3050     function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
  3051       if (!rawQuery) {
  3052         return;
  3054       var state = getSearchState(cm);
  3055       var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);
  3056       if (!query) {
  3057         return;
  3059       highlightSearchMatches(cm, query);
  3060       if (regexEqual(query, state.getQuery())) {
  3061         return query;
  3063       state.setQuery(query);
  3064       return query;
  3066     function searchOverlay(query) {
  3067       if (query.source.charAt(0) == '^') {
  3068         var matchSol = true;
  3070       return {
  3071         token: function(stream) {
  3072           if (matchSol && !stream.sol()) {
  3073             stream.skipToEnd();
  3074             return;
  3076           var match = stream.match(query, false);
  3077           if (match) {
  3078             if (match[0].length == 0) {
  3079               // Matched empty string, skip to next.
  3080               stream.next();
  3081               return 'searching';
  3083             if (!stream.sol()) {
  3084               // Backtrack 1 to match \b
  3085               stream.backUp(1);
  3086               if (!query.exec(stream.next() + match[0])) {
  3087                 stream.next();
  3088                 return null;
  3091             stream.match(query);
  3092             return 'searching';
  3094           while (!stream.eol()) {
  3095             stream.next();
  3096             if (stream.match(query, false)) break;
  3098         },
  3099         query: query
  3100       };
  3102     function highlightSearchMatches(cm, query) {
  3103       var overlay = getSearchState(cm).getOverlay();
  3104       if (!overlay || query != overlay.query) {
  3105         if (overlay) {
  3106           cm.removeOverlay(overlay);
  3108         overlay = searchOverlay(query);
  3109         cm.addOverlay(overlay);
  3110         getSearchState(cm).setOverlay(overlay);
  3113     function findNext(cm, prev, query, repeat) {
  3114       if (repeat === undefined) { repeat = 1; }
  3115       return cm.operation(function() {
  3116         var pos = cm.getCursor();
  3117         var cursor = cm.getSearchCursor(query, pos);
  3118         for (var i = 0; i < repeat; i++) {
  3119           var found = cursor.find(prev);
  3120           if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); }
  3121           if (!found) {
  3122             // SearchCursor may have returned null because it hit EOF, wrap
  3123             // around and try again.
  3124             cursor = cm.getSearchCursor(query,
  3125                 (prev) ? Pos(cm.lastLine()) : Pos(cm.firstLine(), 0) );
  3126             if (!cursor.find(prev)) {
  3127               return;
  3131         return cursor.from();
  3132       });
  3134     function clearSearchHighlight(cm) {
  3135       cm.removeOverlay(getSearchState(cm).getOverlay());
  3136       getSearchState(cm).setOverlay(null);
  3138     /**
  3139      * Check if pos is in the specified range, INCLUSIVE.
  3140      * Range can be specified with 1 or 2 arguments.
  3141      * If the first range argument is an array, treat it as an array of line
  3142      * numbers. Match pos against any of the lines.
  3143      * If the first range argument is a number,
  3144      *   if there is only 1 range argument, check if pos has the same line
  3145      *       number
  3146      *   if there are 2 range arguments, then check if pos is in between the two
  3147      *       range arguments.
  3148      */
  3149     function isInRange(pos, start, end) {
  3150       if (typeof pos != 'number') {
  3151         // Assume it is a cursor position. Get the line number.
  3152         pos = pos.line;
  3154       if (start instanceof Array) {
  3155         return inArray(pos, start);
  3156       } else {
  3157         if (end) {
  3158           return (pos >= start && pos <= end);
  3159         } else {
  3160           return pos == start;
  3164     function getUserVisibleLines(cm) {
  3165       var scrollInfo = cm.getScrollInfo();
  3166       var occludeToleranceTop = 6;
  3167       var occludeToleranceBottom = 10;
  3168       var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
  3169       var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
  3170       var to = cm.coordsChar({left:0, top: bottomY}, 'local');
  3171       return {top: from.line, bottom: to.line};
  3174     // Ex command handling
  3175     // Care must be taken when adding to the default Ex command map. For any
  3176     // pair of commands that have a shared prefix, at least one of their
  3177     // shortNames must not match the prefix of the other command.
  3178     var defaultExCommandMap = [
  3179       { name: 'map' },
  3180       { name: 'nmap', shortName: 'nm' },
  3181       { name: 'vmap', shortName: 'vm' },
  3182       { name: 'unmap' },
  3183       { name: 'write', shortName: 'w' },
  3184       { name: 'undo', shortName: 'u' },
  3185       { name: 'redo', shortName: 'red' },
  3186       { name: 'set', shortName: 'set' },
  3187       { name: 'sort', shortName: 'sor' },
  3188       { name: 'substitute', shortName: 's' },
  3189       { name: 'nohlsearch', shortName: 'noh' },
  3190       { name: 'delmarks', shortName: 'delm' },
  3191       { name: 'registers', shortName: 'reg' }
  3192     ];
  3193     Vim.ExCommandDispatcher = function() {
  3194       this.buildCommandMap_();
  3195     };
  3196     Vim.ExCommandDispatcher.prototype = {
  3197       processCommand: function(cm, input) {
  3198         var vim = cm.state.vim;
  3199         if (vim.visualMode) {
  3200           exitVisualMode(cm);
  3202         var inputStream = new CodeMirror.StringStream(input);
  3203         var params = {};
  3204         params.input = input;
  3205         try {
  3206           this.parseInput_(cm, inputStream, params);
  3207         } catch(e) {
  3208           showConfirm(cm, e);
  3209           throw e;
  3211         var commandName;
  3212         if (!params.commandName) {
  3213           // If only a line range is defined, move to the line.
  3214           if (params.line !== undefined) {
  3215             commandName = 'move';
  3217         } else {
  3218           var command = this.matchCommand_(params.commandName);
  3219           if (command) {
  3220             commandName = command.name;
  3221             this.parseCommandArgs_(inputStream, params, command);
  3222             if (command.type == 'exToKey') {
  3223               // Handle Ex to Key mapping.
  3224               for (var i = 0; i < command.toKeys.length; i++) {
  3225                 CodeMirror.Vim.handleKey(cm, command.toKeys[i]);
  3227               return;
  3228             } else if (command.type == 'exToEx') {
  3229               // Handle Ex to Ex mapping.
  3230               this.processCommand(cm, command.toInput);
  3231               return;
  3235         if (!commandName) {
  3236           showConfirm(cm, 'Not an editor command ":' + input + '"');
  3237           return;
  3239         try {
  3240           exCommands[commandName](cm, params);
  3241         } catch(e) {
  3242           showConfirm(cm, e);
  3243           throw e;
  3245       },
  3246       parseInput_: function(cm, inputStream, result) {
  3247         inputStream.eatWhile(':');
  3248         // Parse range.
  3249         if (inputStream.eat('%')) {
  3250           result.line = cm.firstLine();
  3251           result.lineEnd = cm.lastLine();
  3252         } else {
  3253           result.line = this.parseLineSpec_(cm, inputStream);
  3254           if (result.line !== undefined && inputStream.eat(',')) {
  3255             result.lineEnd = this.parseLineSpec_(cm, inputStream);
  3259         // Parse command name.
  3260         var commandMatch = inputStream.match(/^(\w+)/);
  3261         if (commandMatch) {
  3262           result.commandName = commandMatch[1];
  3263         } else {
  3264           result.commandName = inputStream.match(/.*/)[0];
  3267         return result;
  3268       },
  3269       parseLineSpec_: function(cm, inputStream) {
  3270         var numberMatch = inputStream.match(/^(\d+)/);
  3271         if (numberMatch) {
  3272           return parseInt(numberMatch[1], 10) - 1;
  3274         switch (inputStream.next()) {
  3275           case '.':
  3276             return cm.getCursor().line;
  3277           case '$':
  3278             return cm.lastLine();
  3279           case '\'':
  3280             var mark = cm.state.vim.marks[inputStream.next()];
  3281             if (mark && mark.find()) {
  3282               return mark.find().line;
  3284             throw new Error('Mark not set');
  3285           default:
  3286             inputStream.backUp(1);
  3287             return undefined;
  3289       },
  3290       parseCommandArgs_: function(inputStream, params, command) {
  3291         if (inputStream.eol()) {
  3292           return;
  3294         params.argString = inputStream.match(/.*/)[0];
  3295         // Parse command-line arguments
  3296         var delim = command.argDelimiter || /\s+/;
  3297         var args = trim(params.argString).split(delim);
  3298         if (args.length && args[0]) {
  3299           params.args = args;
  3301       },
  3302       matchCommand_: function(commandName) {
  3303         // Return the command in the command map that matches the shortest
  3304         // prefix of the passed in command name. The match is guaranteed to be
  3305         // unambiguous if the defaultExCommandMap's shortNames are set up
  3306         // correctly. (see @code{defaultExCommandMap}).
  3307         for (var i = commandName.length; i > 0; i--) {
  3308           var prefix = commandName.substring(0, i);
  3309           if (this.commandMap_[prefix]) {
  3310             var command = this.commandMap_[prefix];
  3311             if (command.name.indexOf(commandName) === 0) {
  3312               return command;
  3316         return null;
  3317       },
  3318       buildCommandMap_: function() {
  3319         this.commandMap_ = {};
  3320         for (var i = 0; i < defaultExCommandMap.length; i++) {
  3321           var command = defaultExCommandMap[i];
  3322           var key = command.shortName || command.name;
  3323           this.commandMap_[key] = command;
  3325       },
  3326       map: function(lhs, rhs, ctx) {
  3327         if (lhs != ':' && lhs.charAt(0) == ':') {
  3328           if (ctx) { throw Error('Mode not supported for ex mappings'); }
  3329           var commandName = lhs.substring(1);
  3330           if (rhs != ':' && rhs.charAt(0) == ':') {
  3331             // Ex to Ex mapping
  3332             this.commandMap_[commandName] = {
  3333               name: commandName,
  3334               type: 'exToEx',
  3335               toInput: rhs.substring(1),
  3336               user: true
  3337             };
  3338           } else {
  3339             // Ex to key mapping
  3340             this.commandMap_[commandName] = {
  3341               name: commandName,
  3342               type: 'exToKey',
  3343               toKeys: parseKeyString(rhs),
  3344               user: true
  3345             };
  3347         } else {
  3348           if (rhs != ':' && rhs.charAt(0) == ':') {
  3349             // Key to Ex mapping.
  3350             var mapping = {
  3351               keys: parseKeyString(lhs),
  3352               type: 'keyToEx',
  3353               exArgs: { input: rhs.substring(1) },
  3354               user: true};
  3355             if (ctx) { mapping.context = ctx; }
  3356             defaultKeymap.unshift(mapping);
  3357           } else {
  3358             // Key to key mapping
  3359             var mapping = {
  3360               keys: parseKeyString(lhs),
  3361               type: 'keyToKey',
  3362               toKeys: parseKeyString(rhs),
  3363               user: true
  3364             };
  3365             if (ctx) { mapping.context = ctx; }
  3366             defaultKeymap.unshift(mapping);
  3369       },
  3370       unmap: function(lhs, ctx) {
  3371         var arrayEquals = function(a, b) {
  3372           if (a === b) return true;
  3373           if (a == null || b == null) return true;
  3374           if (a.length != b.length) return false;
  3375           for (var i = 0; i < a.length; i++) {
  3376             if (a[i] !== b[i]) return false;
  3378           return true;
  3379         };
  3380         if (lhs != ':' && lhs.charAt(0) == ':') {
  3381           // Ex to Ex or Ex to key mapping
  3382           if (ctx) { throw Error('Mode not supported for ex mappings'); }
  3383           var commandName = lhs.substring(1);
  3384           if (this.commandMap_[commandName] && this.commandMap_[commandName].user) {
  3385             delete this.commandMap_[commandName];
  3386             return;
  3388         } else {
  3389           // Key to Ex or key to key mapping
  3390           var keys = parseKeyString(lhs);
  3391           for (var i = 0; i < defaultKeymap.length; i++) {
  3392             if (arrayEquals(keys, defaultKeymap[i].keys)
  3393                 && defaultKeymap[i].context === ctx
  3394                 && defaultKeymap[i].user) {
  3395               defaultKeymap.splice(i, 1);
  3396               return;
  3400         throw Error('No such mapping.');
  3402     };
  3404     // Converts a key string sequence of the form a<C-w>bd<Left> into Vim's
  3405     // keymap representation.
  3406     function parseKeyString(str) {
  3407       var key, match;
  3408       var keys = [];
  3409       while (str) {
  3410         match = (/<\w+-.+?>|<\w+>|./).exec(str);
  3411         if (match === null)break;
  3412         key = match[0];
  3413         str = str.substring(match.index + key.length);
  3414         keys.push(key);
  3416       return keys;
  3419     var exCommands = {
  3420       map: function(cm, params, ctx) {
  3421         var mapArgs = params.args;
  3422         if (!mapArgs || mapArgs.length < 2) {
  3423           if (cm) {
  3424             showConfirm(cm, 'Invalid mapping: ' + params.input);
  3426           return;
  3428         exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx);
  3429       },
  3430       nmap: function(cm, params) { this.map(cm, params, 'normal'); },
  3431       vmap: function(cm, params) { this.map(cm, params, 'visual'); },
  3432       unmap: function(cm, params, ctx) {
  3433         var mapArgs = params.args;
  3434         if (!mapArgs || mapArgs.length < 1) {
  3435           if (cm) {
  3436             showConfirm(cm, 'No such mapping: ' + params.input);
  3438           return;
  3440         exCommandDispatcher.unmap(mapArgs[0], ctx);
  3441       },
  3442       move: function(cm, params) {
  3443         commandDispatcher.processCommand(cm, cm.state.vim, {
  3444             type: 'motion',
  3445             motion: 'moveToLineOrEdgeOfDocument',
  3446             motionArgs: { forward: false, explicitRepeat: true,
  3447               linewise: true },
  3448             repeatOverride: params.line+1});
  3449       },
  3450       set: function(cm, params) {
  3451         var setArgs = params.args;
  3452         if (!setArgs || setArgs.length < 1) {
  3453           if (cm) {
  3454             showConfirm(cm, 'Invalid mapping: ' + params.input);
  3456           return;
  3458         var expr = setArgs[0].split('=');
  3459         var optionName = expr[0];
  3460         var value = expr[1];
  3461         var forceGet = false;
  3463         if (optionName.charAt(optionName.length - 1) == '?') {
  3464           // If post-fixed with ?, then the set is actually a get.
  3465           if (value) { throw Error('Trailing characters: ' + params.argString); }
  3466           optionName = optionName.substring(0, optionName.length - 1);
  3467           forceGet = true;
  3469         if (value === undefined && optionName.substring(0, 2) == 'no') {
  3470           // To set boolean options to false, the option name is prefixed with
  3471           // 'no'.
  3472           optionName = optionName.substring(2);
  3473           value = false;
  3475         var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean';
  3476         if (optionIsBoolean && value == undefined) {
  3477           // Calling set with a boolean option sets it to true.
  3478           value = true;
  3480         if (!optionIsBoolean && !value || forceGet) {
  3481           var oldValue = getOption(optionName);
  3482           // If no value is provided, then we assume this is a get.
  3483           if (oldValue === true || oldValue === false) {
  3484             showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName);
  3485           } else {
  3486             showConfirm(cm, '  ' + optionName + '=' + oldValue);
  3488         } else {
  3489           setOption(optionName, value);
  3491       },
  3492       registers: function(cm,params) {
  3493         var regArgs = params.args;
  3494         var registers = vimGlobalState.registerController.registers;
  3495         var regInfo = '----------Registers----------<br><br>';
  3496         if (!regArgs) {
  3497           for (var registerName in registers) {
  3498             var text = registers[registerName].toString();
  3499             if (text.length) {
  3500               regInfo += '"' + registerName + '    ' + text + '<br>';
  3503         } else {
  3504           var registerName;
  3505           regArgs = regArgs.join('');
  3506           for (var i = 0; i < regArgs.length; i++) {
  3507             registerName = regArgs.charAt(i);
  3508             if (!vimGlobalState.registerController.isValidRegister(registerName)) {
  3509               continue;
  3511             var register = registers[registerName] || new Register();
  3512             regInfo += '"' + registerName + '    ' + register.text + '<br>';
  3515         showConfirm(cm, regInfo);
  3516       },
  3517       sort: function(cm, params) {
  3518         var reverse, ignoreCase, unique, number;
  3519         function parseArgs() {
  3520           if (params.argString) {
  3521             var args = new CodeMirror.StringStream(params.argString);
  3522             if (args.eat('!')) { reverse = true; }
  3523             if (args.eol()) { return; }
  3524             if (!args.eatSpace()) { return 'Invalid arguments'; }
  3525             var opts = args.match(/[a-z]+/);
  3526             if (opts) {
  3527               opts = opts[0];
  3528               ignoreCase = opts.indexOf('i') != -1;
  3529               unique = opts.indexOf('u') != -1;
  3530               var decimal = opts.indexOf('d') != -1 && 1;
  3531               var hex = opts.indexOf('x') != -1 && 1;
  3532               var octal = opts.indexOf('o') != -1 && 1;
  3533               if (decimal + hex + octal > 1) { return 'Invalid arguments'; }
  3534               number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
  3536             if (args.eatSpace() && args.match(/\/.*\//)) { 'patterns not supported'; }
  3539         var err = parseArgs();
  3540         if (err) {
  3541           showConfirm(cm, err + ': ' + params.argString);
  3542           return;
  3544         var lineStart = params.line || cm.firstLine();
  3545         var lineEnd = params.lineEnd || params.line || cm.lastLine();
  3546         if (lineStart == lineEnd) { return; }
  3547         var curStart = Pos(lineStart, 0);
  3548         var curEnd = Pos(lineEnd, lineLength(cm, lineEnd));
  3549         var text = cm.getRange(curStart, curEnd).split('\n');
  3550         var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ :
  3551            (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :
  3552            (number == 'octal') ? /([0-7]+)/ : null;
  3553         var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;
  3554         var numPart = [], textPart = [];
  3555         if (number) {
  3556           for (var i = 0; i < text.length; i++) {
  3557             if (numberRegex.exec(text[i])) {
  3558               numPart.push(text[i]);
  3559             } else {
  3560               textPart.push(text[i]);
  3563         } else {
  3564           textPart = text;
  3566         function compareFn(a, b) {
  3567           if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
  3568           if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }
  3569           var anum = number && numberRegex.exec(a);
  3570           var bnum = number && numberRegex.exec(b);
  3571           if (!anum) { return a < b ? -1 : 1; }
  3572           anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);
  3573           bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);
  3574           return anum - bnum;
  3576         numPart.sort(compareFn);
  3577         textPart.sort(compareFn);
  3578         text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);
  3579         if (unique) { // Remove duplicate lines
  3580           var textOld = text;
  3581           var lastLine;
  3582           text = [];
  3583           for (var i = 0; i < textOld.length; i++) {
  3584             if (textOld[i] != lastLine) {
  3585               text.push(textOld[i]);
  3587             lastLine = textOld[i];
  3590         cm.replaceRange(text.join('\n'), curStart, curEnd);
  3591       },
  3592       substitute: function(cm, params) {
  3593         if (!cm.getSearchCursor) {
  3594           throw new Error('Search feature not available. Requires searchcursor.js or ' +
  3595               'any other getSearchCursor implementation.');
  3597         var argString = params.argString;
  3598         var slashes = findUnescapedSlashes(argString);
  3599         if (slashes[0] !== 0) {
  3600           showConfirm(cm, 'Substitutions should be of the form ' +
  3601               ':s/pattern/replace/');
  3602           return;
  3604         var regexPart = argString.substring(slashes[0] + 1, slashes[1]);
  3605         var replacePart = '';
  3606         var flagsPart;
  3607         var count;
  3608         var confirm = false; // Whether to confirm each replace.
  3609         if (slashes[1]) {
  3610           replacePart = argString.substring(slashes[1] + 1, slashes[2]);
  3611           if (getOption('pcre')) {
  3612             replacePart = unescapeRegexReplace(replacePart);
  3613           } else {
  3614             replacePart = translateRegexReplace(replacePart);
  3617         if (slashes[2]) {
  3618           // After the 3rd slash, we can have flags followed by a space followed
  3619           // by count.
  3620           var trailing = argString.substring(slashes[2] + 1).split(' ');
  3621           flagsPart = trailing[0];
  3622           count = parseInt(trailing[1]);
  3624         if (flagsPart) {
  3625           if (flagsPart.indexOf('c') != -1) {
  3626             confirm = true;
  3627             flagsPart.replace('c', '');
  3629           regexPart = regexPart + '/' + flagsPart;
  3631         if (regexPart) {
  3632           // If regex part is empty, then use the previous query. Otherwise use
  3633           // the regex part as the new query.
  3634           try {
  3635             updateSearchQuery(cm, regexPart, true /** ignoreCase */,
  3636               true /** smartCase */);
  3637           } catch (e) {
  3638             showConfirm(cm, 'Invalid regex: ' + regexPart);
  3639             return;
  3642         var state = getSearchState(cm);
  3643         var query = state.getQuery();
  3644         var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
  3645         var lineEnd = params.lineEnd || lineStart;
  3646         if (count) {
  3647           lineStart = lineEnd;
  3648           lineEnd = lineStart + count - 1;
  3650         var startPos = clipCursorToContent(cm, Pos(lineStart, 0));
  3651         var cursor = cm.getSearchCursor(query, startPos);
  3652         doReplace(cm, confirm, lineStart, lineEnd, cursor, query, replacePart);
  3653       },
  3654       redo: CodeMirror.commands.redo,
  3655       undo: CodeMirror.commands.undo,
  3656       write: function(cm) {
  3657         if (CodeMirror.commands.save) {
  3658           // If a save command is defined, call it.
  3659           CodeMirror.commands.save(cm);
  3660         } else {
  3661           // Saves to text area if no save command is defined.
  3662           cm.save();
  3664       },
  3665       nohlsearch: function(cm) {
  3666         clearSearchHighlight(cm);
  3667       },
  3668       delmarks: function(cm, params) {
  3669         if (!params.argString || !trim(params.argString)) {
  3670           showConfirm(cm, 'Argument required');
  3671           return;
  3674         var state = cm.state.vim;
  3675         var stream = new CodeMirror.StringStream(trim(params.argString));
  3676         while (!stream.eol()) {
  3677           stream.eatSpace();
  3679           // Record the streams position at the beginning of the loop for use
  3680           // in error messages.
  3681           var count = stream.pos;
  3683           if (!stream.match(/[a-zA-Z]/, false)) {
  3684             showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
  3685             return;
  3688           var sym = stream.next();
  3689           // Check if this symbol is part of a range
  3690           if (stream.match('-', true)) {
  3691             // This symbol is part of a range.
  3693             // The range must terminate at an alphabetic character.
  3694             if (!stream.match(/[a-zA-Z]/, false)) {
  3695               showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
  3696               return;
  3699             var startMark = sym;
  3700             var finishMark = stream.next();
  3701             // The range must terminate at an alphabetic character which
  3702             // shares the same case as the start of the range.
  3703             if (isLowerCase(startMark) && isLowerCase(finishMark) ||
  3704                 isUpperCase(startMark) && isUpperCase(finishMark)) {
  3705               var start = startMark.charCodeAt(0);
  3706               var finish = finishMark.charCodeAt(0);
  3707               if (start >= finish) {
  3708                 showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
  3709                 return;
  3712               // Because marks are always ASCII values, and we have
  3713               // determined that they are the same case, we can use
  3714               // their char codes to iterate through the defined range.
  3715               for (var j = 0; j <= finish - start; j++) {
  3716                 var mark = String.fromCharCode(start + j);
  3717                 delete state.marks[mark];
  3719             } else {
  3720               showConfirm(cm, 'Invalid argument: ' + startMark + '-');
  3721               return;
  3723           } else {
  3724             // This symbol is a valid mark, and is not part of a range.
  3725             delete state.marks[sym];
  3729     };
  3731     var exCommandDispatcher = new Vim.ExCommandDispatcher();
  3733     /**
  3734     * @param {CodeMirror} cm CodeMirror instance we are in.
  3735     * @param {boolean} confirm Whether to confirm each replace.
  3736     * @param {Cursor} lineStart Line to start replacing from.
  3737     * @param {Cursor} lineEnd Line to stop replacing at.
  3738     * @param {RegExp} query Query for performing matches with.
  3739     * @param {string} replaceWith Text to replace matches with. May contain $1,
  3740     *     $2, etc for replacing captured groups using Javascript replace.
  3741     */
  3742     function doReplace(cm, confirm, lineStart, lineEnd, searchCursor, query,
  3743         replaceWith) {
  3744       // Set up all the functions.
  3745       cm.state.vim.exMode = true;
  3746       var done = false;
  3747       var lastPos = searchCursor.from();
  3748       function replaceAll() {
  3749         cm.operation(function() {
  3750           while (!done) {
  3751             replace();
  3752             next();
  3754           stop();
  3755         });
  3757       function replace() {
  3758         var text = cm.getRange(searchCursor.from(), searchCursor.to());
  3759         var newText = text.replace(query, replaceWith);
  3760         searchCursor.replace(newText);
  3762       function next() {
  3763         var found = searchCursor.findNext();
  3764         if (!found) {
  3765           done = true;
  3766         } else if (isInRange(searchCursor.from(), lineStart, lineEnd)) {
  3767           cm.scrollIntoView(searchCursor.from(), 30);
  3768           cm.setSelection(searchCursor.from(), searchCursor.to());
  3769           lastPos = searchCursor.from();
  3770           done = false;
  3771         } else {
  3772           done = true;
  3775       function stop(close) {
  3776         if (close) { close(); }
  3777         cm.focus();
  3778         if (lastPos) {
  3779           cm.setCursor(lastPos);
  3780           var vim = cm.state.vim;
  3781           vim.exMode = false;
  3782           vim.lastHPos = vim.lastHSPos = lastPos.ch;
  3785       function onPromptKeyDown(e, _value, close) {
  3786         // Swallow all keys.
  3787         CodeMirror.e_stop(e);
  3788         var keyName = CodeMirror.keyName(e);
  3789         switch (keyName) {
  3790           case 'Y':
  3791             replace(); next(); break;
  3792           case 'N':
  3793             next(); break;
  3794           case 'A':
  3795             cm.operation(replaceAll); break;
  3796           case 'L':
  3797             replace();
  3798             // fall through and exit.
  3799           case 'Q':
  3800           case 'Esc':
  3801           case 'Ctrl-C':
  3802           case 'Ctrl-[':
  3803             stop(close);
  3804             break;
  3806         if (done) { stop(close); }
  3809       // Actually do replace.
  3810       next();
  3811       if (done) {
  3812         showConfirm(cm, 'No matches for ' + query.source);
  3813         return;
  3815       if (!confirm) {
  3816         replaceAll();
  3817         return;
  3819       showPrompt(cm, {
  3820         prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)',
  3821         onKeyDown: onPromptKeyDown
  3822       });
  3825     // Register Vim with CodeMirror
  3826     function buildVimKeyMap() {
  3827       /**
  3828        * Handle the raw key event from CodeMirror. Translate the
  3829        * Shift + key modifier to the resulting letter, while preserving other
  3830        * modifers.
  3831        */
  3832       function cmKeyToVimKey(key, modifier) {
  3833         var vimKey = key;
  3834         if (isUpperCase(vimKey) && modifier == 'Ctrl') {
  3835             vimKey = vimKey.toLowerCase();
  3837         if (modifier) {
  3838           // Vim will parse modifier+key combination as a single key.
  3839           vimKey = modifier.charAt(0) + '-' + vimKey;
  3841         var specialKey = ({Enter:'CR',Backspace:'BS',Delete:'Del'})[vimKey];
  3842         vimKey = specialKey ? specialKey : vimKey;
  3843         vimKey = vimKey.length > 1 ? '<'+ vimKey + '>' : vimKey;
  3844         return vimKey;
  3847       // Closure to bind CodeMirror, key, modifier.
  3848       function keyMapper(vimKey) {
  3849         return function(cm) {
  3850           CodeMirror.Vim.handleKey(cm, vimKey);
  3851         };
  3854       var cmToVimKeymap = {
  3855         'nofallthrough': true,
  3856         'style': 'fat-cursor'
  3857       };
  3858       function bindKeys(keys, modifier) {
  3859         for (var i = 0; i < keys.length; i++) {
  3860           var key = keys[i];
  3861           if (!modifier && key.length == 1) {
  3862             // Wrap all keys without modifiers with '' to identify them by their
  3863             // key characters instead of key identifiers.
  3864             key = "'" + key + "'";
  3866           var vimKey = cmKeyToVimKey(keys[i], modifier);
  3867           var cmKey = modifier ? modifier + '-' + key : key;
  3868           cmToVimKeymap[cmKey] = keyMapper(vimKey);
  3871       bindKeys(upperCaseAlphabet);
  3872       bindKeys(lowerCaseAlphabet);
  3873       bindKeys(upperCaseAlphabet, 'Ctrl');
  3874       bindKeys(specialSymbols);
  3875       bindKeys(specialSymbols, 'Ctrl');
  3876       bindKeys(numbers);
  3877       bindKeys(numbers, 'Ctrl');
  3878       bindKeys(specialKeys);
  3879       bindKeys(specialKeys, 'Ctrl');
  3880       return cmToVimKeymap;
  3882     CodeMirror.keyMap.vim = buildVimKeyMap();
  3884     function exitInsertMode(cm) {
  3885       var vim = cm.state.vim;
  3886       var macroModeState = vimGlobalState.macroModeState;
  3887       var isPlaying = macroModeState.isPlaying;
  3888       if (!isPlaying) {
  3889         cm.off('change', onChange);
  3890         cm.off('cursorActivity', onCursorActivity);
  3891         CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
  3893       if (!isPlaying && vim.insertModeRepeat > 1) {
  3894         // Perform insert mode repeat for commands like 3,a and 3,o.
  3895         repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
  3896             true /** repeatForInsert */);
  3897         vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;
  3899       delete vim.insertModeRepeat;
  3900       cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1);
  3901       vim.insertMode = false;
  3902       cm.setOption('keyMap', 'vim');
  3903       cm.setOption('disableInput', true);
  3904       cm.toggleOverwrite(false); // exit replace mode if we were in it.
  3905       CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
  3906       if (macroModeState.isRecording) {
  3907         logInsertModeChange(macroModeState);
  3911     CodeMirror.keyMap['vim-insert'] = {
  3912       // TODO: override navigation keys so that Esc will cancel automatic
  3913       // indentation from o, O, i_<CR>
  3914       'Esc': exitInsertMode,
  3915       'Ctrl-[': exitInsertMode,
  3916       'Ctrl-C': exitInsertMode,
  3917       'Ctrl-N': 'autocomplete',
  3918       'Ctrl-P': 'autocomplete',
  3919       'Enter': function(cm) {
  3920         var fn = CodeMirror.commands.newlineAndIndentContinueComment ||
  3921             CodeMirror.commands.newlineAndIndent;
  3922         fn(cm);
  3923       },
  3924       fallthrough: ['default']
  3925     };
  3927     CodeMirror.keyMap['vim-replace'] = {
  3928       'Backspace': 'goCharLeft',
  3929       fallthrough: ['vim-insert']
  3930     };
  3932     function executeMacroRegister(cm, vim, macroModeState, registerName) {
  3933       var register = vimGlobalState.registerController.getRegister(registerName);
  3934       var keyBuffer = register.keyBuffer;
  3935       var imc = 0;
  3936       macroModeState.isPlaying = true;
  3937       for (var i = 0; i < keyBuffer.length; i++) {
  3938         var text = keyBuffer[i];
  3939         var match, key;
  3940         while (text) {
  3941           // Pull off one command key, which is either a single character
  3942           // or a special sequence wrapped in '<' and '>', e.g. '<Space>'.
  3943           match = (/<\w+-.+?>|<\w+>|./).exec(text);
  3944           key = match[0];
  3945           text = text.substring(match.index + key.length);
  3946           CodeMirror.Vim.handleKey(cm, key);
  3947           if (vim.insertMode) {
  3948             repeatInsertModeChanges(
  3949                 cm, register.insertModeChanges[imc++].changes, 1);
  3950             exitInsertMode(cm);
  3953       };
  3954       macroModeState.isPlaying = false;
  3957     function logKey(macroModeState, key) {
  3958       if (macroModeState.isPlaying) { return; }
  3959       var registerName = macroModeState.latestRegister;
  3960       var register = vimGlobalState.registerController.getRegister(registerName);
  3961       if (register) {
  3962         register.pushText(key);
  3966     function logInsertModeChange(macroModeState) {
  3967       if (macroModeState.isPlaying) { return; }
  3968       var registerName = macroModeState.latestRegister;
  3969       var register = vimGlobalState.registerController.getRegister(registerName);
  3970       if (register) {
  3971         register.pushInsertModeChanges(macroModeState.lastInsertModeChanges);
  3975     /**
  3976      * Listens for changes made in insert mode.
  3977      * Should only be active in insert mode.
  3978      */
  3979     function onChange(_cm, changeObj) {
  3980       var macroModeState = vimGlobalState.macroModeState;
  3981       var lastChange = macroModeState.lastInsertModeChanges;
  3982       if (!macroModeState.isPlaying) {
  3983         while(changeObj) {
  3984           lastChange.expectCursorActivityForChange = true;
  3985           if (changeObj.origin == '+input' || changeObj.origin == 'paste'
  3986               || changeObj.origin === undefined /* only in testing */) {
  3987             var text = changeObj.text.join('\n');
  3988             lastChange.changes.push(text);
  3990           // Change objects may be chained with next.
  3991           changeObj = changeObj.next;
  3996     /**
  3997     * Listens for any kind of cursor activity on CodeMirror.
  3998     * - For tracking cursor activity in insert mode.
  3999     * - Should only be active in insert mode.
  4000     */
  4001     function onCursorActivity() {
  4002       var macroModeState = vimGlobalState.macroModeState;
  4003       if (macroModeState.isPlaying) { return; }
  4004       var lastChange = macroModeState.lastInsertModeChanges;
  4005       if (lastChange.expectCursorActivityForChange) {
  4006         lastChange.expectCursorActivityForChange = false;
  4007       } else {
  4008         // Cursor moved outside the context of an edit. Reset the change.
  4009         lastChange.changes = [];
  4013     /** Wrapper for special keys pressed in insert mode */
  4014     function InsertModeKey(keyName) {
  4015       this.keyName = keyName;
  4018     /**
  4019     * Handles raw key down events from the text area.
  4020     * - Should only be active in insert mode.
  4021     * - For recording deletes in insert mode.
  4022     */
  4023     function onKeyEventTargetKeyDown(e) {
  4024       var macroModeState = vimGlobalState.macroModeState;
  4025       var lastChange = macroModeState.lastInsertModeChanges;
  4026       var keyName = CodeMirror.keyName(e);
  4027       function onKeyFound() {
  4028         lastChange.changes.push(new InsertModeKey(keyName));
  4029         return true;
  4031       if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
  4032         CodeMirror.lookupKey(keyName, ['vim-insert'], onKeyFound);
  4036     /**
  4037      * Repeats the last edit, which includes exactly 1 command and at most 1
  4038      * insert. Operator and motion commands are read from lastEditInputState,
  4039      * while action commands are read from lastEditActionCommand.
  4041      * If repeatForInsert is true, then the function was called by
  4042      * exitInsertMode to repeat the insert mode changes the user just made. The
  4043      * corresponding enterInsertMode call was made with a count.
  4044      */
  4045     function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
  4046       var macroModeState = vimGlobalState.macroModeState;
  4047       macroModeState.isPlaying = true;
  4048       var isAction = !!vim.lastEditActionCommand;
  4049       var cachedInputState = vim.inputState;
  4050       function repeatCommand() {
  4051         if (isAction) {
  4052           commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);
  4053         } else {
  4054           commandDispatcher.evalInput(cm, vim);
  4057       function repeatInsert(repeat) {
  4058         if (macroModeState.lastInsertModeChanges.changes.length > 0) {
  4059           // For some reason, repeat cw in desktop VIM does not repeat
  4060           // insert mode changes. Will conform to that behavior.
  4061           repeat = !vim.lastEditActionCommand ? 1 : repeat;
  4062           var changeObject = macroModeState.lastInsertModeChanges;
  4063           // This isn't strictly necessary, but since lastInsertModeChanges is
  4064           // supposed to be immutable during replay, this helps catch bugs.
  4065           macroModeState.lastInsertModeChanges = {};
  4066           repeatInsertModeChanges(cm, changeObject.changes, repeat);
  4067           macroModeState.lastInsertModeChanges = changeObject;
  4070       vim.inputState = vim.lastEditInputState;
  4071       if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {
  4072         // o and O repeat have to be interlaced with insert repeats so that the
  4073         // insertions appear on separate lines instead of the last line.
  4074         for (var i = 0; i < repeat; i++) {
  4075           repeatCommand();
  4076           repeatInsert(1);
  4078       } else {
  4079         if (!repeatForInsert) {
  4080           // Hack to get the cursor to end up at the right place. If I is
  4081           // repeated in insert mode repeat, cursor will be 1 insert
  4082           // change set left of where it should be.
  4083           repeatCommand();
  4085         repeatInsert(repeat);
  4087       vim.inputState = cachedInputState;
  4088       if (vim.insertMode && !repeatForInsert) {
  4089         // Don't exit insert mode twice. If repeatForInsert is set, then we
  4090         // were called by an exitInsertMode call lower on the stack.
  4091         exitInsertMode(cm);
  4093       macroModeState.isPlaying = false;
  4094     };
  4096     function repeatInsertModeChanges(cm, changes, repeat) {
  4097       function keyHandler(binding) {
  4098         if (typeof binding == 'string') {
  4099           CodeMirror.commands[binding](cm);
  4100         } else {
  4101           binding(cm);
  4103         return true;
  4105       for (var i = 0; i < repeat; i++) {
  4106         for (var j = 0; j < changes.length; j++) {
  4107           var change = changes[j];
  4108           if (change instanceof InsertModeKey) {
  4109             CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler);
  4110           } else {
  4111             var cur = cm.getCursor();
  4112             cm.replaceRange(change, cur, cur);
  4118     resetVimGlobalState();
  4119     return vimApi;
  4120   };
  4121   // Initialize Vim and make it available as an API.
  4122   CodeMirror.Vim = Vim();
  4123 });

mercurial