public/lib/CodeMirror/keymap/vim.js   F
last analyzed

Complexity

Total Complexity 1280
Complexity/F 4.4

Size

Lines of Code 5024
Function Count 291

Duplication

Duplicated Lines 38
Ratio 0.76 %

Importance

Changes 0
Metric Value
cc 0
eloc 3475
nc 0
dl 38
loc 5024
rs 0.8
c 0
b 0
f 0
wmc 1280
mnd 7
bc 1024
fnc 291
bpm 3.5189
cpm 4.3986
noi 90

1 Function

Rating   Name   Duplication   Size   Complexity  
B vim.js ➔ ?!? 38 5017 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like public/lib/CodeMirror/keymap/vim.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2
// Distributed under an MIT license: http://codemirror.net/LICENSE
3
4
/**
5
 * Supported keybindings:
6
 *   Too many to list. Refer to defaultKeyMap below.
7
 *
8
 * Supported Ex commands:
9
 *   Refer to defaultExCommandMap below.
10
 *
11
 * Registers: unnamed, -, a-z, A-Z, 0-9
12
 *   (Does not respect the special case for number registers when delete
13
 *    operator is made with these commands: %, (, ),  , /, ?, n, N, {, } )
14
 *   TODO: Implement the remaining registers.
15
 *
16
 * Marks: a-z, A-Z, and 0-9
17
 *   TODO: Implement the remaining special marks. They have more complex
18
 *       behavior.
19
 *
20
 * Events:
21
 *  'vim-mode-change' - raised on the editor anytime the current mode changes,
22
 *                      Event object: {mode: "visual", subMode: "linewise"}
23
 *
24
 * Code structure:
25
 *  1. Default keymap
26
 *  2. Variable declarations and short basic helpers
27
 *  3. Instance (External API) implementation
28
 *  4. Internal state tracking objects (input state, counter) implementation
29
 *     and instanstiation
30
 *  5. Key handler (the main command dispatcher) implementation
31
 *  6. Motion, operator, and action implementations
32
 *  7. Helper functions for the key handler, motions, operators, and actions
33
 *  8. Set up Vim to work as a keymap for CodeMirror.
34
 *  9. Ex command implementations.
35
 */
36
37
(function(mod) {
38
  if (typeof exports == "object" && typeof module == "object") // CommonJS
39
    mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog"), require("../addon/edit/matchbrackets.js"));
40
  else if (typeof define == "function" && define.amd) // AMD
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
41
    define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog", "../addon/edit/matchbrackets"], mod);
42
  else // Plain browser env
43
    mod(CodeMirror);
0 ignored issues
show
Bug introduced by
The variable CodeMirror seems to be never declared. If this is a global, consider adding a /** global: CodeMirror */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
44
})(function(CodeMirror) {
45
  'use strict';
46
47
  var defaultKeymap = [
48
    // Key to key mapping. This goes first to make it possible to override
49
    // existing mappings.
50
    { keys: '<Left>', type: 'keyToKey', toKeys: 'h' },
51
    { keys: '<Right>', type: 'keyToKey', toKeys: 'l' },
52
    { keys: '<Up>', type: 'keyToKey', toKeys: 'k' },
53
    { keys: '<Down>', type: 'keyToKey', toKeys: 'j' },
54
    { keys: '<Space>', type: 'keyToKey', toKeys: 'l' },
55
    { keys: '<BS>', type: 'keyToKey', toKeys: 'h', context: 'normal'},
56
    { keys: '<C-Space>', type: 'keyToKey', toKeys: 'W' },
57
    { keys: '<C-BS>', type: 'keyToKey', toKeys: 'B', context: 'normal' },
58
    { keys: '<S-Space>', type: 'keyToKey', toKeys: 'w' },
59
    { keys: '<S-BS>', type: 'keyToKey', toKeys: 'b', context: 'normal' },
60
    { keys: '<C-n>', type: 'keyToKey', toKeys: 'j' },
61
    { keys: '<C-p>', type: 'keyToKey', toKeys: 'k' },
62
    { keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>' },
63
    { keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>' },
64
    { keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },
65
    { keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },
66
    { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' },
67
    { keys: 's', type: 'keyToKey', toKeys: 'xi', context: 'visual'},
68
    { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' },
69
    { keys: 'S', type: 'keyToKey', toKeys: 'dcc', context: 'visual' },
70
    { keys: '<Home>', type: 'keyToKey', toKeys: '0' },
71
    { keys: '<End>', type: 'keyToKey', toKeys: '$' },
72
    { keys: '<PageUp>', type: 'keyToKey', toKeys: '<C-b>' },
73
    { keys: '<PageDown>', type: 'keyToKey', toKeys: '<C-f>' },
74
    { keys: '<CR>', type: 'keyToKey', toKeys: 'j^', context: 'normal' },
75
    // Motions
76
    { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }},
77
    { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }},
78
    { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }},
79
    { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }},
80
    { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }},
81
    { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }},
82
    { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }},
83
    { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }},
84
    { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }},
85
    { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }},
86
    { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }},
87
    { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }},
88
    { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }},
89
    { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }},
90
    { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }},
91
    { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }},
92
    { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }},
93
    { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }},
94
    { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }},
95
    { keys: '<C-f>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }},
96
    { keys: '<C-b>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }},
97
    { keys: '<C-d>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }},
98
    { keys: '<C-u>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }},
99
    { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
100
    { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
101
    { keys: '0', type: 'motion', motion: 'moveToStartOfLine' },
102
    { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' },
103
    { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }},
104
    { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }},
105
    { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
106
    { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }},
107
    { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }},
108
    { keys: 'f<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }},
109
    { keys: 'F<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }},
110
    { keys: 't<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }},
111
    { keys: 'T<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }},
112
    { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }},
113
    { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }},
114
    { keys: '\'<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}},
115
    { keys: '`<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}},
116
    { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
117
    { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
118
    { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
119
    { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
120
    // the next two aren't motions but must come before more general motion declarations
121
    { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}},
122
    { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}},
123
    { keys: ']<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}},
124
    { keys: '[<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}},
125
    { keys: '|', type: 'motion', motion: 'moveToColumn'},
126
    { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'},
127
    { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'},
128
    // Operators
129
    { keys: 'd', type: 'operator', operator: 'delete' },
130
    { keys: 'y', type: 'operator', operator: 'yank' },
131
    { keys: 'c', type: 'operator', operator: 'change' },
132
    { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }},
133
    { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }},
134
    { keys: 'g~', type: 'operator', operator: 'changeCase' },
135
    { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true },
136
    { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true },
137
    { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }},
138
    { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }},
139
    // Operator-Motion dual commands
140
    { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }},
141
    { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }},
142
    { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
143
    { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'},
144
    { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
145
    { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'},
146
    { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
147
    { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'},
148
    { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'},
149
    { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'},
150
    { keys: '<C-w>', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },
151
    // Actions
152
    { keys: '<C-i>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},
153
    { keys: '<C-o>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }},
154
    { keys: '<C-e>', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }},
155
    { keys: '<C-y>', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }},
156
    { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' },
157
    { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
158
    { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
159
    { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
160
    { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' },
161
    { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' },
162
    { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
163
    { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
164
    { keys: 'v', type: 'action', action: 'toggleVisualMode' },
165
    { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }},
166
    { keys: '<C-v>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
167
    { keys: 'gv', type: 'action', action: 'reselectLastSelection' },
168
    { keys: 'J', type: 'action', action: 'joinLines', isEdit: true },
169
    { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }},
170
    { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }},
171
    { keys: 'r<character>', type: 'action', action: 'replace', isEdit: true },
172
    { keys: '@<character>', type: 'action', action: 'replayMacro' },
173
    { keys: 'q<character>', type: 'action', action: 'enterMacroRecordMode' },
174
    // Handle Replace-mode as a special case of insert mode.
175
    { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }},
176
    { keys: 'u', type: 'action', action: 'undo', context: 'normal' },
177
    { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true },
178
    { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true },
179
    { keys: '<C-r>', type: 'action', action: 'redo' },
180
    { keys: 'm<character>', type: 'action', action: 'setMark' },
181
    { keys: '"<character>', type: 'action', action: 'setRegister' },
182
    { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }},
183
    { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
184
    { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }},
185
    { keys: 'z<CR>', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
186
    { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }},
187
    { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
188
    { keys: '.', type: 'action', action: 'repeatLastEdit' },
189
    { keys: '<C-a>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}},
190
    { keys: '<C-x>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}},
191
    // Text object motions
192
    { keys: 'a<character>', type: 'motion', motion: 'textObjectManipulation' },
193
    { keys: 'i<character>', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }},
194
    // Search
195
    { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
196
    { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
197
    { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
198
    { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
199
    { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
200
    { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
201
    // Ex command
202
    { keys: ':', type: 'ex' }
203
  ];
204
205
  /**
206
   * Ex commands
207
   * Care must be taken when adding to the default Ex command map. For any
208
   * pair of commands that have a shared prefix, at least one of their
209
   * shortNames must not match the prefix of the other command.
210
   */
211
  var defaultExCommandMap = [
212
    { name: 'colorscheme', shortName: 'colo' },
213
    { name: 'map' },
214
    { name: 'imap', shortName: 'im' },
215
    { name: 'nmap', shortName: 'nm' },
216
    { name: 'vmap', shortName: 'vm' },
217
    { name: 'unmap' },
218
    { name: 'write', shortName: 'w' },
219
    { name: 'undo', shortName: 'u' },
220
    { name: 'redo', shortName: 'red' },
221
    { name: 'set', shortName: 'se' },
222
    { name: 'set', shortName: 'se' },
223
    { name: 'setlocal', shortName: 'setl' },
224
    { name: 'setglobal', shortName: 'setg' },
225
    { name: 'sort', shortName: 'sor' },
226
    { name: 'substitute', shortName: 's', possiblyAsync: true },
227
    { name: 'nohlsearch', shortName: 'noh' },
228
    { name: 'delmarks', shortName: 'delm' },
229
    { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true },
230
    { name: 'global', shortName: 'g' }
231
  ];
232
233
  var Pos = CodeMirror.Pos;
234
235
  var Vim = function() {
236
    function enterVimMode(cm) {
237
      cm.setOption('disableInput', true);
238
      cm.setOption('showCursorWhenSelecting', false);
239
      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
240
      cm.on('cursorActivity', onCursorActivity);
241
      maybeInitVimState(cm);
242
      CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
243
    }
244
245
    function leaveVimMode(cm) {
246
      cm.setOption('disableInput', false);
247
      cm.off('cursorActivity', onCursorActivity);
248
      CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
249
      cm.state.vim = null;
250
    }
251
252
    function detachVimMap(cm, next) {
253
      if (this == CodeMirror.keyMap.vim)
254
        CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");
255
256
      if (!next || next.attach != attachVimMap)
257
        leaveVimMode(cm, false);
0 ignored issues
show
Bug introduced by
The call to leaveVimMode seems to have too many arguments starting with false.
Loading history...
258
    }
259
    function attachVimMap(cm, prev) {
260
      if (this == CodeMirror.keyMap.vim)
261
        CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");
262
263
      if (!prev || prev.attach != attachVimMap)
264
        enterVimMode(cm);
265
    }
266
267
    // Deprecated, simply setting the keymap works again.
268
    CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {
269
      if (val && cm.getOption("keyMap") != "vim")
270
        cm.setOption("keyMap", "vim");
271
      else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap")))
272
        cm.setOption("keyMap", "default");
273
    });
274
275
    function cmKey(key, cm) {
276
      if (!cm) { return undefined; }
277
      var vimKey = cmKeyToVimKey(key);
278
      if (!vimKey) {
279
        return false;
280
      }
281
      var cmd = CodeMirror.Vim.findKey(cm, vimKey);
282
      if (typeof cmd == 'function') {
283
        CodeMirror.signal(cm, 'vim-keypress', vimKey);
284
      }
285
      return cmd;
286
    }
287
288
    var modifiers = {'Shift': 'S', 'Ctrl': 'C', 'Alt': 'A', 'Cmd': 'D', 'Mod': 'A'};
289
    var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del'};
290
    function cmKeyToVimKey(key) {
291
      if (key.charAt(0) == '\'') {
292
        // Keypress character binding of format "'a'"
293
        return key.charAt(1);
294
      }
295
      var pieces = key.split('-');
296
      if (/-$/.test(key)) {
297
        // If the - key was typed, split will result in 2 extra empty strings
298
        // in the array. Replace them with 1 '-'.
299
        pieces.splice(-2, 2, '-');
300
      }
301
      var lastPiece = pieces[pieces.length - 1];
302
      if (pieces.length == 1 && pieces[0].length == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing pieces.0.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
Best Practice introduced by
Comparing pieces.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
303
        // No-modifier bindings use literal character bindings above. Skip.
304
        return false;
305
      } else if (pieces.length == 2 && pieces[0] == 'Shift' && lastPiece.length == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing lastPiece.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
306
        // Ignore Shift+char bindings as they should be handled by literal character.
307
        return false;
308
      }
309
      var hasCharacter = false;
310
      for (var i = 0; i < pieces.length; i++) {
311
        var piece = pieces[i];
312
        if (piece in modifiers) { pieces[i] = modifiers[piece]; }
313
        else { hasCharacter = true; }
314
        if (piece in specialKeys) { pieces[i] = specialKeys[piece]; }
315
      }
316
      if (!hasCharacter) {
317
        // Vim does not support modifier only keys.
318
        return false;
319
      }
320
      // TODO: Current bindings expect the character to be lower case, but
321
      // it looks like vim key notation uses upper case.
322
      if (isUpperCase(lastPiece)) {
323
        pieces[pieces.length - 1] = lastPiece.toLowerCase();
324
      }
325
      return '<' + pieces.join('-') + '>';
326
    }
327
328
    function getOnPasteFn(cm) {
329
      var vim = cm.state.vim;
330
      if (!vim.onPasteFn) {
331
        vim.onPasteFn = function() {
332
          if (!vim.insertMode) {
333
            cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
334
            actions.enterInsertMode(cm, {}, vim);
335
          }
336
        };
337
      }
338
      return vim.onPasteFn;
339
    }
340
341
    var numberRegex = /[\d]/;
342
    var wordCharTest = [CodeMirror.isWordChar, function(ch) {
343
      return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch);
344
    }], bigWordCharTest = [function(ch) {
345
      return /\S/.test(ch);
346
    }];
347
    function makeKeyRange(start, size) {
348
      var keys = [];
349
      for (var i = start; i < start + size; i++) {
350
        keys.push(String.fromCharCode(i));
351
      }
352
      return keys;
353
    }
354
    var upperCaseAlphabet = makeKeyRange(65, 26);
355
    var lowerCaseAlphabet = makeKeyRange(97, 26);
356
    var numbers = makeKeyRange(48, 10);
357
    var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
358
    var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':', '/']);
359
360
    function isLine(cm, line) {
361
      return line >= cm.firstLine() && line <= cm.lastLine();
362
    }
363
    function isLowerCase(k) {
364
      return (/^[a-z]$/).test(k);
365
    }
366
    function isMatchableSymbol(k) {
367
      return '()[]{}'.indexOf(k) != -1;
368
    }
369
    function isNumber(k) {
370
      return numberRegex.test(k);
371
    }
372
    function isUpperCase(k) {
373
      return (/^[A-Z]$/).test(k);
374
    }
375
    function isWhiteSpaceString(k) {
376
      return (/^\s*$/).test(k);
377
    }
378
    function inArray(val, arr) {
379
      for (var i = 0; i < arr.length; i++) {
380
        if (arr[i] == val) {
381
          return true;
382
        }
383
      }
384
      return false;
385
    }
386
387
    var options = {};
388
    function defineOption(name, defaultValue, type, aliases, callback) {
389
      if (defaultValue === undefined && !callback) {
390
        throw Error('defaultValue is required unless callback is provided');
391
      }
392
      if (!type) { type = 'string'; }
393
      options[name] = {
394
        type: type,
395
        defaultValue: defaultValue,
396
        callback: callback
397
      };
398
      if (aliases) {
399
        for (var i = 0; i < aliases.length; i++) {
400
          options[aliases[i]] = options[name];
401
        }
402
      }
403
      if (defaultValue) {
404
        setOption(name, defaultValue);
405
      }
406
    }
407
408
    function setOption(name, value, cm, cfg) {
409
      var option = options[name];
410
      cfg = cfg || {};
411
      var scope = cfg.scope;
412
      if (!option) {
413
        throw Error('Unknown option: ' + name);
414
      }
415
      if (option.type == 'boolean') {
416
        if (value && value !== true) {
417
          throw Error('Invalid argument: ' + name + '=' + value);
418
        } else if (value !== false) {
419
          // Boolean options are set to true if value is not defined.
420
          value = true;
421
        }
422
      }
423 View Code Duplication
      if (option.callback) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
424
        if (scope !== 'local') {
425
          option.callback(value, undefined);
426
        }
427
        if (scope !== 'global' && cm) {
428
          option.callback(value, cm);
429
        }
430
      } else {
431
        if (scope !== 'local') {
432
          option.value = option.type == 'boolean' ? !!value : value;
433
        }
434
        if (scope !== 'global' && cm) {
435
          cm.state.vim.options[name] = {value: value};
436
        }
437
      }
438
    }
439
440
    function getOption(name, cm, cfg) {
441
      var option = options[name];
442
      cfg = cfg || {};
443
      var scope = cfg.scope;
444
      if (!option) {
445
        throw Error('Unknown option: ' + name);
446
      }
447 View Code Duplication
      if (option.callback) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
448
        var local = cm && option.callback(undefined, cm);
449
        if (scope !== 'global' && local !== undefined) {
450
          return local;
451
        }
452
        if (scope !== 'local') {
453
          return option.callback();
454
        }
455
        return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
456
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
457
        var local = (scope !== 'global') && (cm && cm.state.vim.options[name]);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable local already seems to be declared on line 448. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
458
        return (local || (scope !== 'local') && option || {}).value;
459
      }
460
    }
461
462
    defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) {
463
      // Option is local. Do nothing for global.
464
      if (cm === undefined) {
465
        return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
466
      }
467
      // The 'filetype' option proxies to the CodeMirror 'mode' option.
468
      if (name === undefined) {
469
        var mode = cm.getOption('mode');
470
        return mode == 'null' ? '' : mode;
471
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
472
        var mode = name == '' ? 'null' : name;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable mode already seems to be declared on line 469. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
473
        cm.setOption('mode', mode);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
474
      }
475
    });
476
477
    var createCircularJumpList = function() {
478
      var size = 100;
479
      var pointer = -1;
480
      var head = 0;
481
      var tail = 0;
482
      var buffer = new Array(size);
483
      function add(cm, oldCur, newCur) {
484
        var current = pointer % size;
485
        var curMark = buffer[current];
486
        function useNextSlot(cursor) {
487
          var next = ++pointer % size;
488
          var trashMark = buffer[next];
489
          if (trashMark) {
490
            trashMark.clear();
491
          }
492
          buffer[next] = cm.setBookmark(cursor);
493
        }
494
        if (curMark) {
495
          var markPos = curMark.find();
496
          // avoid recording redundant cursor position
497
          if (markPos && !cursorEqual(markPos, oldCur)) {
498
            useNextSlot(oldCur);
499
          }
500
        } else {
501
          useNextSlot(oldCur);
502
        }
503
        useNextSlot(newCur);
504
        head = pointer;
505
        tail = pointer - size + 1;
506
        if (tail < 0) {
507
          tail = 0;
508
        }
509
      }
510
      function move(cm, offset) {
511
        pointer += offset;
512
        if (pointer > head) {
513
          pointer = head;
514
        } else if (pointer < tail) {
515
          pointer = tail;
516
        }
517
        var mark = buffer[(size + pointer) % size];
518
        // skip marks that are temporarily removed from text buffer
519
        if (mark && !mark.find()) {
520
          var inc = offset > 0 ? 1 : -1;
521
          var newCur;
522
          var oldCur = cm.getCursor();
523
          do {
524
            pointer += inc;
0 ignored issues
show
Bug introduced by
The variable pointer is changed as part of the loop loop for example by inc on line 524. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
525
            mark = buffer[(size + pointer) % size];
526
            // skip marks that are the same as current position
527
            if (mark &&
528
                (newCur = mark.find()) &&
529
                !cursorEqual(oldCur, newCur)) {
530
              break;
531
            }
532
          } while (pointer < head && pointer > tail);
533
        }
534
        return mark;
535
      }
536
      return {
537
        cachedCursor: undefined, //used for # and * jumps
538
        add: add,
539
        move: move
540
      };
541
    };
542
543
    // Returns an object to track the changes associated insert mode.  It
544
    // clones the object that is passed in, or creates an empty object one if
545
    // none is provided.
546
    var createInsertModeChanges = function(c) {
547
      if (c) {
548
        // Copy construction
549
        return {
550
          changes: c.changes,
551
          expectCursorActivityForChange: c.expectCursorActivityForChange
552
        };
553
      }
554
      return {
555
        // Change list
556
        changes: [],
557
        // Set to true on change, false on cursorActivity.
558
        expectCursorActivityForChange: false
559
      };
560
    };
561
562
    function MacroModeState() {
563
      this.latestRegister = undefined;
564
      this.isPlaying = false;
565
      this.isRecording = false;
566
      this.replaySearchQueries = [];
567
      this.onRecordingDone = undefined;
568
      this.lastInsertModeChanges = createInsertModeChanges();
569
    }
570
    MacroModeState.prototype = {
571
      exitMacroRecordMode: function() {
572
        var macroModeState = vimGlobalState.macroModeState;
573
        if (macroModeState.onRecordingDone) {
574
          macroModeState.onRecordingDone(); // close dialog
575
        }
576
        macroModeState.onRecordingDone = undefined;
577
        macroModeState.isRecording = false;
578
      },
579
      enterMacroRecordMode: function(cm, registerName) {
580
        var register =
581
            vimGlobalState.registerController.getRegister(registerName);
582
        if (register) {
583
          register.clear();
584
          this.latestRegister = registerName;
585
          if (cm.openDialog) {
586
            this.onRecordingDone = cm.openDialog(
587
                '(recording)['+registerName+']', null, {bottom:true});
588
          }
589
          this.isRecording = true;
590
        }
591
      }
592
    };
593
594
    function maybeInitVimState(cm) {
595
      if (!cm.state.vim) {
596
        // Store instance state in the CodeMirror object.
597
        cm.state.vim = {
598
          inputState: new InputState(),
599
          // Vim's input state that triggered the last edit, used to repeat
600
          // motions and operators with '.'.
601
          lastEditInputState: undefined,
602
          // Vim's action command before the last edit, used to repeat actions
603
          // with '.' and insert mode repeat.
604
          lastEditActionCommand: undefined,
605
          // When using jk for navigation, if you move from a longer line to a
606
          // shorter line, the cursor may clip to the end of the shorter line.
607
          // If j is pressed again and cursor goes to the next line, the
608
          // cursor should go back to its horizontal position on the longer
609
          // line if it can. This is to keep track of the horizontal position.
610
          lastHPos: -1,
611
          // Doing the same with screen-position for gj/gk
612
          lastHSPos: -1,
613
          // The last motion command run. Cleared if a non-motion command gets
614
          // executed in between.
615
          lastMotion: null,
616
          marks: {},
617
          // Mark for rendering fake cursor for visual mode.
618
          fakeCursor: null,
619
          insertMode: false,
620
          // Repeat count for changes made in insert mode, triggered by key
621
          // sequences like 3,i. Only exists when insertMode is true.
622
          insertModeRepeat: undefined,
623
          visualMode: false,
624
          // If we are in visual line mode. No effect if visualMode is false.
625
          visualLine: false,
626
          visualBlock: false,
627
          lastSelection: null,
628
          lastPastedText: null,
629
          sel: {},
630
          // Buffer-local/window-local values of vim options.
631
          options: {}
632
        };
633
      }
634
      return cm.state.vim;
635
    }
636
    var vimGlobalState;
637
    function resetVimGlobalState() {
638
      vimGlobalState = {
639
        // The current search query.
640
        searchQuery: null,
641
        // Whether we are searching backwards.
642
        searchIsReversed: false,
643
        // Replace part of the last substituted pattern
644
        lastSubstituteReplacePart: undefined,
645
        jumpList: createCircularJumpList(),
646
        macroModeState: new MacroModeState,
647
        // Recording latest f, t, F or T motion command.
648
        lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
649
        registerController: new RegisterController({}),
650
        // search history buffer
651
        searchHistoryController: new HistoryController({}),
652
        // ex Command history buffer
653
        exCommandHistoryController : new HistoryController({})
654
      };
655
      for (var optionName in options) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
656
        var option = options[optionName];
657
        option.value = option.defaultValue;
658
      }
659
    }
660
661
    var lastInsertModeKeyTimer;
662
    var vimApi= {
663
      buildKeyMap: function() {
664
        // TODO: Convert keymap into dictionary format for fast lookup.
665
      },
666
      // Testing hook, though it might be useful to expose the register
667
      // controller anyways.
668
      getRegisterController: function() {
669
        return vimGlobalState.registerController;
670
      },
671
      // Testing hook.
672
      resetVimGlobalState_: resetVimGlobalState,
673
674
      // Testing hook.
675
      getVimGlobalState_: function() {
676
        return vimGlobalState;
677
      },
678
679
      // Testing hook.
680
      maybeInitVimState_: maybeInitVimState,
681
682
      suppressErrorLogging: false,
683
684
      InsertModeKey: InsertModeKey,
685
      map: function(lhs, rhs, ctx) {
686
        // Add user defined key bindings.
687
        exCommandDispatcher.map(lhs, rhs, ctx);
688
      },
689
      // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace
690
      // them, or somehow make them work with the existing CodeMirror setOption/getOption API.
691
      setOption: setOption,
692
      getOption: getOption,
693
      defineOption: defineOption,
694
      defineEx: function(name, prefix, func){
695
        if (!prefix) {
696
          prefix = name;
697
        } else if (name.indexOf(prefix) !== 0) {
698
          throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
699
        }
700
        exCommands[name]=func;
701
        exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
702
      },
703
      handleKey: function (cm, key, origin) {
704
        var command = this.findKey(cm, key, origin);
705
        if (typeof command === 'function') {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if typeof command === "function" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
706
          return command();
707
        }
708
      },
709
      /**
710
       * This is the outermost function called by CodeMirror, after keys have
711
       * been mapped to their Vim equivalents.
712
       *
713
       * Finds a command based on the key (and cached keys if there is a
714
       * multi-key sequence). Returns `undefined` if no key is matched, a noop
715
       * function if a partial match is found (multi-key), and a function to
716
       * execute the bound command if a a key is matched. The function always
717
       * returns true.
718
       */
719
      findKey: function(cm, key, origin) {
720
        var vim = maybeInitVimState(cm);
721
        function handleMacroRecording() {
722
          var macroModeState = vimGlobalState.macroModeState;
723
          if (macroModeState.isRecording) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if macroModeState.isRecording is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
724
            if (key == 'q') {
725
              macroModeState.exitMacroRecordMode();
726
              clearInputState(cm);
727
              return true;
728
            }
729
            if (origin != 'mapping') {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if origin != "mapping" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
730
              logKey(macroModeState, key);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
731
            }
732
          }
733
        }
734
        function handleEsc() {
735
          if (key == '<Esc>') {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if key == "<Esc>" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
736
            // Clear input state and get back to normal mode.
737
            clearInputState(cm);
738
            if (vim.visualMode) {
739
              exitVisualMode(cm);
740
            } else if (vim.insertMode) {
741
              exitInsertMode(cm);
742
            }
743
            return true;
744
          }
745
        }
746
        function doKeyToKey(keys) {
747
          // TODO: prevent infinite recursion.
748
          var match;
749
          while (keys) {
750
            // Pull off one command key, which is either a single character
751
            // or a special sequence wrapped in '<' and '>', e.g. '<Space>'.
752
            match = (/<\w+-.+?>|<\w+>|./).exec(keys);
753
            key = match[0];
754
            keys = keys.substring(match.index + key.length);
0 ignored issues
show
Bug introduced by
The variable key is changed as part of the while loop for example by match.0 on line 753. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
755
            CodeMirror.Vim.handleKey(cm, key, 'mapping');
756
          }
757
        }
758
759
        function handleKeyInsertMode() {
760
          if (handleEsc()) { return true; }
761
          var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
762
          var keysAreChars = key.length == 1;
0 ignored issues
show
Best Practice introduced by
Comparing key.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
763
          var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
764
          // Need to check all key substrings in insert mode.
765
          while (keys.length > 1 && match.type != 'full') {
766
            var keys = vim.inputState.keyBuffer = keys.slice(1);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable keys already seems to be declared on line 761. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
767
            var thisMatch = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
768
            if (thisMatch.type != 'none') { match = thisMatch; }
769
          }
770
          if (match.type == 'none') { clearInputState(cm); return false; }
771
          else if (match.type == 'partial') {
772
            if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
773
            lastInsertModeKeyTimer = window.setTimeout(
774
              function() { if (vim.insertMode && vim.inputState.keyBuffer) { clearInputState(cm); } },
775
              getOption('insertModeEscKeysTimeout'));
776
            return !keysAreChars;
777
          }
778
779
          if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
780
          if (keysAreChars) {
781
            var here = cm.getCursor();
782
            cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input');
783
          }
784
          clearInputState(cm);
785
          return match.command;
786
        }
787
788
        function handleKeyNonInsertMode() {
789
          if (handleMacroRecording() || handleEsc()) { return true; };
790
791
          var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
792
          if (/^[1-9]\d*$/.test(keys)) { return true; }
793
794
          var keysMatcher = /^(\d*)(.*)$/.exec(keys);
795
          if (!keysMatcher) { clearInputState(cm); return false; }
796
          var context = vim.visualMode ? 'visual' :
797
                                         'normal';
798
          var match = commandDispatcher.matchCommand(keysMatcher[2] || keysMatcher[1], defaultKeymap, vim.inputState, context);
799
          if (match.type == 'none') { clearInputState(cm); return false; }
800
          else if (match.type == 'partial') { return true; }
801
802
          vim.inputState.keyBuffer = '';
803
          var keysMatcher = /^(\d*)(.*)$/.exec(keys);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable keysMatcher already seems to be declared on line 794. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
804
          if (keysMatcher[1] && keysMatcher[1] != '0') {
805
            vim.inputState.pushRepeatDigit(keysMatcher[1]);
806
          }
807
          return match.command;
808
        }
809
810
        var command;
811
        if (vim.insertMode) { command = handleKeyInsertMode(); }
812
        else { command = handleKeyNonInsertMode(); }
813
        if (command === false) {
814
          return undefined;
815
        } else if (command === true) {
816
          // TODO: Look into using CodeMirror's multi-key handling.
817
          // Return no-op since we are caching the key. Counts as handled, but
818
          // don't want act on it just yet.
819
          return function() {};
820
        } else {
821
          return function() {
822
            return cm.operation(function() {
823
              cm.curOp.isVimOp = true;
824
              try {
825
                if (command.type == 'keyToKey') {
826
                  doKeyToKey(command.toKeys);
827
                } else {
828
                  commandDispatcher.processCommand(cm, vim, command);
829
                }
830
              } catch (e) {
831
                // clear VIM state in case it's in a bad state.
832
                cm.state.vim = undefined;
833
                maybeInitVimState(cm);
834
                if (!CodeMirror.Vim.suppressErrorLogging) {
835
                  console['log'](e);
836
                }
837
                throw e;
838
              }
839
              return true;
840
            });
841
          };
842
        }
843
      },
844
      handleEx: function(cm, input) {
845
        exCommandDispatcher.processCommand(cm, input);
846
      },
847
848
      defineMotion: defineMotion,
849
      defineAction: defineAction,
850
      defineOperator: defineOperator,
851
      mapCommand: mapCommand,
852
      _mapCommand: _mapCommand,
853
854
      defineRegister: defineRegister,
855
856
      exitVisualMode: exitVisualMode,
857
      exitInsertMode: exitInsertMode
858
    };
859
860
    // Represents the current input state.
861
    function InputState() {
862
      this.prefixRepeat = [];
863
      this.motionRepeat = [];
864
865
      this.operator = null;
866
      this.operatorArgs = null;
867
      this.motion = null;
868
      this.motionArgs = null;
869
      this.keyBuffer = []; // For matching multi-key commands.
870
      this.registerName = null; // Defaults to the unnamed register.
871
    }
872
    InputState.prototype.pushRepeatDigit = function(n) {
873
      if (!this.operator) {
874
        this.prefixRepeat = this.prefixRepeat.concat(n);
875
      } else {
876
        this.motionRepeat = this.motionRepeat.concat(n);
877
      }
878
    };
879
    InputState.prototype.getRepeat = function() {
880
      var repeat = 0;
881
      if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {
882
        repeat = 1;
883
        if (this.prefixRepeat.length > 0) {
884
          repeat *= parseInt(this.prefixRepeat.join(''), 10);
885
        }
886
        if (this.motionRepeat.length > 0) {
887
          repeat *= parseInt(this.motionRepeat.join(''), 10);
888
        }
889
      }
890
      return repeat;
891
    };
892
893
    function clearInputState(cm, reason) {
894
      cm.state.vim.inputState = new InputState();
895
      CodeMirror.signal(cm, 'vim-command-done', reason);
896
    }
897
898
    /*
899
     * Register stores information about copy and paste registers.  Besides
900
     * text, a register must store whether it is linewise (i.e., when it is
901
     * pasted, should it insert itself into a new line, or should the text be
902
     * inserted at the cursor position.)
903
     */
904
    function Register(text, linewise, blockwise) {
905
      this.clear();
906
      this.keyBuffer = [text || ''];
907
      this.insertModeChanges = [];
908
      this.searchQueries = [];
909
      this.linewise = !!linewise;
910
      this.blockwise = !!blockwise;
911
    }
912
    Register.prototype = {
913
      setText: function(text, linewise, blockwise) {
914
        this.keyBuffer = [text || ''];
915
        this.linewise = !!linewise;
916
        this.blockwise = !!blockwise;
917
      },
918
      pushText: function(text, linewise) {
919
        // if this register has ever been set to linewise, use linewise.
920
        if (linewise) {
921
          if (!this.linewise) {
922
            this.keyBuffer.push('\n');
923
          }
924
          this.linewise = true;
925
        }
926
        this.keyBuffer.push(text);
927
      },
928
      pushInsertModeChanges: function(changes) {
929
        this.insertModeChanges.push(createInsertModeChanges(changes));
930
      },
931
      pushSearchQuery: function(query) {
932
        this.searchQueries.push(query);
933
      },
934
      clear: function() {
935
        this.keyBuffer = [];
936
        this.insertModeChanges = [];
937
        this.searchQueries = [];
938
        this.linewise = false;
939
      },
940
      toString: function() {
941
        return this.keyBuffer.join('');
942
      }
943
    };
944
945
    /**
946
     * Defines an external register.
947
     *
948
     * The name should be a single character that will be used to reference the register.
949
     * The register should support setText, pushText, clear, and toString(). See Register
950
     * for a reference implementation.
951
     */
952
    function defineRegister(name, register) {
953
      var registers = vimGlobalState.registerController.registers[name];
954
      if (!name || name.length != 1) {
0 ignored issues
show
Best Practice introduced by
Comparing name.length to 1 using the != operator is not safe. Consider using !== instead.
Loading history...
955
        throw Error('Register name must be 1 character');
956
      }
957
      if (registers[name]) {
958
        throw Error('Register already defined ' + name);
959
      }
960
      registers[name] = register;
961
      validRegisters.push(name);
962
    }
963
964
    /*
965
     * vim registers allow you to keep many independent copy and paste buffers.
966
     * See http://usevim.com/2012/04/13/registers/ for an introduction.
967
     *
968
     * RegisterController keeps the state of all the registers.  An initial
969
     * state may be passed in.  The unnamed register '"' will always be
970
     * overridden.
971
     */
972
    function RegisterController(registers) {
973
      this.registers = registers;
974
      this.unnamedRegister = registers['"'] = new Register();
975
      registers['.'] = new Register();
976
      registers[':'] = new Register();
977
      registers['/'] = new Register();
978
    }
979
    RegisterController.prototype = {
980
      pushText: function(registerName, operator, text, linewise, blockwise) {
981
        if (linewise && text.charAt(0) == '\n') {
982
          text = text.slice(1) + '\n';
983
        }
984
        if (linewise && text.charAt(text.length - 1) !== '\n'){
985
          text += '\n';
986
        }
987
        // Lowercase and uppercase registers refer to the same register.
988
        // Uppercase just means append.
989
        var register = this.isValidRegister(registerName) ?
990
            this.getRegister(registerName) : null;
991
        // if no register/an invalid register was specified, things go to the
992
        // default registers
993
        if (!register) {
994
          switch (operator) {
995
            case 'yank':
996
              // The 0 register contains the text from the most recent yank.
997
              this.registers['0'] = new Register(text, linewise, blockwise);
998
              break;
999
            case 'delete':
1000
            case 'change':
1001
              if (text.indexOf('\n') == -1) {
1002
                // Delete less than 1 line. Update the small delete register.
1003
                this.registers['-'] = new Register(text, linewise);
1004
              } else {
1005
                // Shift down the contents of the numbered registers and put the
1006
                // deleted text into register 1.
1007
                this.shiftNumericRegisters_();
1008
                this.registers['1'] = new Register(text, linewise);
1009
              }
1010
              break;
1011
          }
1012
          // Make sure the unnamed register is set to what just happened
1013
          this.unnamedRegister.setText(text, linewise, blockwise);
1014
          return;
1015
        }
1016
1017
        // If we've gotten to this point, we've actually specified a register
1018
        var append = isUpperCase(registerName);
1019
        if (append) {
1020
          register.pushText(text, linewise);
1021
        } else {
1022
          register.setText(text, linewise, blockwise);
1023
        }
1024
        // The unnamed register always has the same value as the last used
1025
        // register.
1026
        this.unnamedRegister.setText(register.toString(), linewise);
1027
      },
1028
      // Gets the register named @name.  If one of @name doesn't already exist,
1029
      // create it.  If @name is invalid, return the unnamedRegister.
1030
      getRegister: function(name) {
1031
        if (!this.isValidRegister(name)) {
1032
          return this.unnamedRegister;
1033
        }
1034
        name = name.toLowerCase();
1035
        if (!this.registers[name]) {
1036
          this.registers[name] = new Register();
1037
        }
1038
        return this.registers[name];
1039
      },
1040
      isValidRegister: function(name) {
1041
        return name && inArray(name, validRegisters);
1042
      },
1043
      shiftNumericRegisters_: function() {
1044
        for (var i = 9; i >= 2; i--) {
1045
          this.registers[i] = this.getRegister('' + (i - 1));
1046
        }
1047
      }
1048
    };
1049
    function HistoryController() {
1050
        this.historyBuffer = [];
1051
        this.iterator;
0 ignored issues
show
introduced by
The result of the property access to this.iterator is not used.
Loading history...
1052
        this.initialPrefix = null;
1053
    }
1054
    HistoryController.prototype = {
1055
      // the input argument here acts a user entered prefix for a small time
1056
      // until we start autocompletion in which case it is the autocompleted.
1057
      nextMatch: function (input, up) {
1058
        var historyBuffer = this.historyBuffer;
1059
        var dir = up ? -1 : 1;
1060
        if (this.initialPrefix === null) this.initialPrefix = input;
1061
        for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i+= dir) {
1062
          var element = historyBuffer[i];
1063
          for (var j = 0; j <= element.length; j++) {
1064
            if (this.initialPrefix == element.substring(0, j)) {
1065
              this.iterator = i;
1066
              return element;
1067
            }
1068
          }
1069
        }
1070
        // should return the user input in case we reach the end of buffer.
1071
        if (i >= historyBuffer.length) {
1072
          this.iterator = historyBuffer.length;
1073
          return this.initialPrefix;
1074
        }
1075
        // return the last autocompleted query or exCommand as it is.
1076
        if (i < 0 ) return input;
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if i < 0 is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
1077
      },
1078
      pushInput: function(input) {
1079
        var index = this.historyBuffer.indexOf(input);
1080
        if (index > -1) this.historyBuffer.splice(index, 1);
1081
        if (input.length) this.historyBuffer.push(input);
1082
      },
1083
      reset: function() {
1084
        this.initialPrefix = null;
1085
        this.iterator = this.historyBuffer.length;
1086
      }
1087
    };
1088
    var commandDispatcher = {
1089
      matchCommand: function(keys, keyMap, inputState, context) {
1090
        var matches = commandMatches(keys, keyMap, context, inputState);
1091
        if (!matches.full && !matches.partial) {
1092
          return {type: 'none'};
1093
        } else if (!matches.full && matches.partial) {
1094
          return {type: 'partial'};
1095
        }
1096
1097
        var bestMatch;
1098
        for (var i = 0; i < matches.full.length; i++) {
1099
          var match = matches.full[i];
1100
          if (!bestMatch) {
1101
            bestMatch = match;
1102
          }
1103
        }
1104
        if (bestMatch.keys.slice(-11) == '<character>') {
0 ignored issues
show
Bug introduced by
The variable bestMatch seems to not be initialized for all possible execution paths.
Loading history...
1105
          inputState.selectedCharacter = lastChar(keys);
1106
        }
1107
        return {type: 'full', command: bestMatch};
1108
      },
1109
      processCommand: function(cm, vim, command) {
1110
        vim.inputState.repeatOverride = command.repeatOverride;
1111
        switch (command.type) {
1112
          case 'motion':
1113
            this.processMotion(cm, vim, command);
1114
            break;
1115
          case 'operator':
1116
            this.processOperator(cm, vim, command);
1117
            break;
1118
          case 'operatorMotion':
1119
            this.processOperatorMotion(cm, vim, command);
1120
            break;
1121
          case 'action':
1122
            this.processAction(cm, vim, command);
1123
            break;
1124
          case 'search':
1125
            this.processSearch(cm, vim, command);
1126
            break;
1127
          case 'ex':
1128
          case 'keyToEx':
1129
            this.processEx(cm, vim, command);
1130
            break;
1131
          default:
1132
            break;
1133
        }
1134
      },
1135
      processMotion: function(cm, vim, command) {
1136
        vim.inputState.motion = command.motion;
1137
        vim.inputState.motionArgs = copyArgs(command.motionArgs);
1138
        this.evalInput(cm, vim);
1139
      },
1140
      processOperator: function(cm, vim, command) {
1141
        var inputState = vim.inputState;
1142
        if (inputState.operator) {
1143
          if (inputState.operator == command.operator) {
1144
            // Typing an operator twice like 'dd' makes the operator operate
1145
            // linewise
1146
            inputState.motion = 'expandToLine';
1147
            inputState.motionArgs = { linewise: true };
1148
            this.evalInput(cm, vim);
1149
            return;
1150
          } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1151
            // 2 different operators in a row doesn't make sense.
1152
            clearInputState(cm);
1153
          }
1154
        }
1155
        inputState.operator = command.operator;
1156
        inputState.operatorArgs = copyArgs(command.operatorArgs);
1157
        if (vim.visualMode) {
1158
          // Operating on a selection in visual mode. We don't need a motion.
1159
          this.evalInput(cm, vim);
1160
        }
1161
      },
1162
      processOperatorMotion: function(cm, vim, command) {
1163
        var visualMode = vim.visualMode;
1164
        var operatorMotionArgs = copyArgs(command.operatorMotionArgs);
1165
        if (operatorMotionArgs) {
1166
          // Operator motions may have special behavior in visual mode.
1167
          if (visualMode && operatorMotionArgs.visualLine) {
1168
            vim.visualLine = true;
1169
          }
1170
        }
1171
        this.processOperator(cm, vim, command);
1172
        if (!visualMode) {
1173
          this.processMotion(cm, vim, command);
1174
        }
1175
      },
1176
      processAction: function(cm, vim, command) {
1177
        var inputState = vim.inputState;
1178
        var repeat = inputState.getRepeat();
1179
        var repeatIsExplicit = !!repeat;
1180
        var actionArgs = copyArgs(command.actionArgs) || {};
1181
        if (inputState.selectedCharacter) {
1182
          actionArgs.selectedCharacter = inputState.selectedCharacter;
1183
        }
1184
        // Actions may or may not have motions and operators. Do these first.
1185
        if (command.operator) {
1186
          this.processOperator(cm, vim, command);
1187
        }
1188
        if (command.motion) {
1189
          this.processMotion(cm, vim, command);
1190
        }
1191
        if (command.motion || command.operator) {
1192
          this.evalInput(cm, vim);
1193
        }
1194
        actionArgs.repeat = repeat || 1;
1195
        actionArgs.repeatIsExplicit = repeatIsExplicit;
1196
        actionArgs.registerName = inputState.registerName;
1197
        clearInputState(cm);
1198
        vim.lastMotion = null;
1199
        if (command.isEdit) {
1200
          this.recordLastEdit(vim, inputState, command);
1201
        }
1202
        actions[command.action](cm, actionArgs, vim);
1203
      },
1204
      processSearch: function(cm, vim, command) {
1205
        if (!cm.getSearchCursor) {
1206
          // Search depends on SearchCursor.
1207
          return;
1208
        }
1209
        var forward = command.searchArgs.forward;
1210
        var wholeWordOnly = command.searchArgs.wholeWordOnly;
1211
        getSearchState(cm).setReversed(!forward);
1212
        var promptPrefix = (forward) ? '/' : '?';
1213
        var originalQuery = getSearchState(cm).getQuery();
1214
        var originalScrollPos = cm.getScrollInfo();
1215
        function handleQuery(query, ignoreCase, smartCase) {
1216
          vimGlobalState.searchHistoryController.pushInput(query);
1217
          vimGlobalState.searchHistoryController.reset();
1218
          try {
1219
            updateSearchQuery(cm, query, ignoreCase, smartCase);
1220
          } catch (e) {
1221
            showConfirm(cm, 'Invalid regex: ' + query);
1222
            clearInputState(cm);
1223
            return;
1224
          }
1225
          commandDispatcher.processMotion(cm, vim, {
1226
            type: 'motion',
1227
            motion: 'findNext',
1228
            motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }
1229
          });
1230
        }
1231
        function onPromptClose(query) {
1232
          cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
1233
          handleQuery(query, true /** ignoreCase */, true /** smartCase */);
1234
          var macroModeState = vimGlobalState.macroModeState;
1235
          if (macroModeState.isRecording) {
1236
            logSearchQuery(macroModeState, query);
1237
          }
1238
        }
1239
        function onPromptKeyUp(e, query, close) {
1240
          var keyName = CodeMirror.keyName(e), up;
1241
          if (keyName == 'Up' || keyName == 'Down') {
1242
            up = keyName == 'Up' ? true : false;
1243
            query = vimGlobalState.searchHistoryController.nextMatch(query, up) || '';
1244
            close(query);
1245
          } else {
1246
            if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')
1247
              vimGlobalState.searchHistoryController.reset();
1248
          }
1249
          var parsedQuery;
1250
          try {
1251
            parsedQuery = updateSearchQuery(cm, query,
1252
                true /** ignoreCase */, true /** smartCase */);
1253
          } catch (e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
1254
            // Swallow bad regexes for incremental search.
1255
          }
1256
          if (parsedQuery) {
1257
            cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);
1258
          } else {
1259
            clearSearchHighlight(cm);
1260
            cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
1261
          }
1262
        }
1263
        function onPromptKeyDown(e, query, close) {
1264
          var keyName = CodeMirror.keyName(e);
1265
          if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||
1266
              (keyName == 'Backspace' && query == '')) {
1267
            vimGlobalState.searchHistoryController.pushInput(query);
1268
            vimGlobalState.searchHistoryController.reset();
1269
            updateSearchQuery(cm, originalQuery);
1270
            clearSearchHighlight(cm);
1271
            cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
1272
            CodeMirror.e_stop(e);
1273
            clearInputState(cm);
1274
            close();
1275
            cm.focus();
1276
          } else if (keyName == 'Ctrl-U') {
1277
            // Ctrl-U clears input.
1278
            CodeMirror.e_stop(e);
1279
            close('');
1280
          }
1281
        }
1282
        switch (command.searchArgs.querySrc) {
1283
          case 'prompt':
1284
            var macroModeState = vimGlobalState.macroModeState;
1285
            if (macroModeState.isPlaying) {
1286
              var query = macroModeState.replaySearchQueries.shift();
1287
              handleQuery(query, true /** ignoreCase */, false /** smartCase */);
1288
            } else {
1289
              showPrompt(cm, {
1290
                  onClose: onPromptClose,
1291
                  prefix: promptPrefix,
1292
                  desc: searchPromptDesc,
1293
                  onKeyUp: onPromptKeyUp,
1294
                  onKeyDown: onPromptKeyDown
1295
              });
1296
            }
1297
            break;
1298
          case 'wordUnderCursor':
1299
            var word = expandWordUnderCursor(cm, false /** inclusive */,
1300
                true /** forward */, false /** bigWord */,
1301
                true /** noSymbol */);
1302
            var isKeyword = true;
1303
            if (!word) {
1304
              word = expandWordUnderCursor(cm, false /** inclusive */,
1305
                  true /** forward */, false /** bigWord */,
1306
                  false /** noSymbol */);
1307
              isKeyword = false;
1308
            }
1309
            if (!word) {
1310
              return;
1311
            }
1312
            var query = cm.getLine(word.start.line).substring(word.start.ch,
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable query already seems to be declared on line 1286. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1313
                word.end.ch);
1314
            if (isKeyword && wholeWordOnly) {
1315
                query = '\\b' + query + '\\b';
1316
            } else {
1317
              query = escapeRegex(query);
1318
            }
1319
1320
            // cachedCursor is used to save the old position of the cursor
1321
            // when * or # causes vim to seek for the nearest word and shift
1322
            // the cursor before entering the motion.
1323
            vimGlobalState.jumpList.cachedCursor = cm.getCursor();
1324
            cm.setCursor(word.start);
1325
1326
            handleQuery(query, true /** ignoreCase */, false /** smartCase */);
1327
            break;
1328
        }
1329
      },
1330
      processEx: function(cm, vim, command) {
1331
        function onPromptClose(input) {
1332
          // Give the prompt some time to close so that if processCommand shows
1333
          // an error, the elements don't overlap.
1334
          vimGlobalState.exCommandHistoryController.pushInput(input);
1335
          vimGlobalState.exCommandHistoryController.reset();
1336
          exCommandDispatcher.processCommand(cm, input);
1337
        }
1338
        function onPromptKeyDown(e, input, close) {
1339
          var keyName = CodeMirror.keyName(e), up;
1340
          if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||
1341
              (keyName == 'Backspace' && input == '')) {
1342
            vimGlobalState.exCommandHistoryController.pushInput(input);
1343
            vimGlobalState.exCommandHistoryController.reset();
1344
            CodeMirror.e_stop(e);
1345
            clearInputState(cm);
1346
            close();
1347
            cm.focus();
1348
          }
1349
          if (keyName == 'Up' || keyName == 'Down') {
1350
            up = keyName == 'Up' ? true : false;
1351
            input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || '';
1352
            close(input);
1353
          } else if (keyName == 'Ctrl-U') {
1354
            // Ctrl-U clears input.
1355
            CodeMirror.e_stop(e);
1356
            close('');
1357
          } else {
1358
            if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')
1359
              vimGlobalState.exCommandHistoryController.reset();
1360
          }
1361
        }
1362
        if (command.type == 'keyToEx') {
1363
          // Handle user defined Ex to Ex mappings
1364
          exCommandDispatcher.processCommand(cm, command.exArgs.input);
1365
        } else {
1366
          if (vim.visualMode) {
1367
            showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
1368
                onKeyDown: onPromptKeyDown});
1369
          } else {
1370
            showPrompt(cm, { onClose: onPromptClose, prefix: ':',
1371
                onKeyDown: onPromptKeyDown});
1372
          }
1373
        }
1374
      },
1375
      evalInput: function(cm, vim) {
1376
        // If the motion comand is set, execute both the operator and motion.
1377
        // Otherwise return.
1378
        var inputState = vim.inputState;
1379
        var motion = inputState.motion;
1380
        var motionArgs = inputState.motionArgs || {};
1381
        var operator = inputState.operator;
1382
        var operatorArgs = inputState.operatorArgs || {};
1383
        var registerName = inputState.registerName;
1384
        var sel = vim.sel;
1385
        // TODO: Make sure cm and vim selections are identical outside visual mode.
1386
        var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head'));
1387
        var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor'));
1388
        var oldHead = copyCursor(origHead);
1389
        var oldAnchor = copyCursor(origAnchor);
1390
        var newHead, newAnchor;
1391
        var repeat;
1392
        if (operator) {
1393
          this.recordLastEdit(vim, inputState);
1394
        }
1395
        if (inputState.repeatOverride !== undefined) {
1396
          // If repeatOverride is specified, that takes precedence over the
1397
          // input state's repeat. Used by Ex mode and can be user defined.
1398
          repeat = inputState.repeatOverride;
1399
        } else {
1400
          repeat = inputState.getRepeat();
1401
        }
1402
        if (repeat > 0 && motionArgs.explicitRepeat) {
1403
          motionArgs.repeatIsExplicit = true;
1404
        } else if (motionArgs.noRepeat ||
1405
            (!motionArgs.explicitRepeat && repeat === 0)) {
1406
          repeat = 1;
1407
          motionArgs.repeatIsExplicit = false;
1408
        }
1409
        if (inputState.selectedCharacter) {
1410
          // If there is a character input, stick it in all of the arg arrays.
1411
          motionArgs.selectedCharacter = operatorArgs.selectedCharacter =
1412
              inputState.selectedCharacter;
1413
        }
1414
        motionArgs.repeat = repeat;
1415
        clearInputState(cm);
1416
        if (motion) {
1417
          var motionResult = motions[motion](cm, origHead, motionArgs, vim);
1418
          vim.lastMotion = motions[motion];
1419
          if (!motionResult) {
1420
            return;
1421
          }
1422
          if (motionArgs.toJumplist) {
1423
            var jumpList = vimGlobalState.jumpList;
1424
            // if the current motion is # or *, use cachedCursor
1425
            var cachedCursor = jumpList.cachedCursor;
1426
            if (cachedCursor) {
1427
              recordJumpPosition(cm, cachedCursor, motionResult);
1428
              delete jumpList.cachedCursor;
1429
            } else {
1430
              recordJumpPosition(cm, origHead, motionResult);
1431
            }
1432
          }
1433
          if (motionResult instanceof Array) {
1434
            newAnchor = motionResult[0];
1435
            newHead = motionResult[1];
1436
          } else {
1437
            newHead = motionResult;
1438
          }
1439
          // TODO: Handle null returns from motion commands better.
1440
          if (!newHead) {
1441
            newHead = copyCursor(origHead);
1442
          }
1443
          if (vim.visualMode) {
1444
            if (!(vim.visualBlock && newHead.ch === Infinity)) {
1445
              newHead = clipCursorToContent(cm, newHead, vim.visualBlock);
1446
            }
1447
            if (newAnchor) {
1448
              newAnchor = clipCursorToContent(cm, newAnchor, true);
1449
            }
1450
            newAnchor = newAnchor || oldAnchor;
1451
            sel.anchor = newAnchor;
1452
            sel.head = newHead;
1453
            updateCmSelection(cm);
1454
            updateMark(cm, vim, '<',
1455
                cursorIsBefore(newAnchor, newHead) ? newAnchor
1456
                    : newHead);
1457
            updateMark(cm, vim, '>',
1458
                cursorIsBefore(newAnchor, newHead) ? newHead
1459
                    : newAnchor);
1460
          } else if (!operator) {
1461
            newHead = clipCursorToContent(cm, newHead);
1462
            cm.setCursor(newHead.line, newHead.ch);
1463
          }
1464
        }
1465
        if (operator) {
1466
          if (operatorArgs.lastSel) {
1467
            // Replaying a visual mode operation
1468
            newAnchor = oldAnchor;
1469
            var lastSel = operatorArgs.lastSel;
1470
            var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line);
1471
            var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch);
1472
            if (lastSel.visualLine) {
1473
              // Linewise Visual mode: The same number of lines.
1474
              newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
1475
            } else if (lastSel.visualBlock) {
1476
              // Blockwise Visual mode: The same number of lines and columns.
1477
              newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset);
1478
            } else if (lastSel.head.line == lastSel.anchor.line) {
1479
              // Normal Visual mode within one line: The same number of characters.
1480
              newHead = Pos(oldAnchor.line, oldAnchor.ch + chOffset);
1481
            } else {
1482
              // Normal Visual mode with several lines: The same number of lines, in the
1483
              // last line the same number of characters as in the last line the last time.
1484
              newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
1485
            }
1486
            vim.visualMode = true;
1487
            vim.visualLine = lastSel.visualLine;
1488
            vim.visualBlock = lastSel.visualBlock;
1489
            sel = vim.sel = {
1490
              anchor: newAnchor,
1491
              head: newHead
1492
            };
1493
            updateCmSelection(cm);
1494
          } else if (vim.visualMode) {
1495
            operatorArgs.lastSel = {
1496
              anchor: copyCursor(sel.anchor),
1497
              head: copyCursor(sel.head),
1498
              visualBlock: vim.visualBlock,
1499
              visualLine: vim.visualLine
1500
            };
1501
          }
1502
          var curStart, curEnd, linewise, mode;
1503
          var cmSel;
1504
          if (vim.visualMode) {
1505
            // Init visual op
1506
            curStart = cursorMin(sel.head, sel.anchor);
1507
            curEnd = cursorMax(sel.head, sel.anchor);
1508
            linewise = vim.visualLine || operatorArgs.linewise;
1509
            mode = vim.visualBlock ? 'block' :
1510
                   linewise ? 'line' :
1511
                   'char';
1512
            cmSel = makeCmSelection(cm, {
1513
              anchor: curStart,
1514
              head: curEnd
1515
            }, mode);
1516
            if (linewise) {
1517
              var ranges = cmSel.ranges;
1518
              if (mode == 'block') {
1519
                // Linewise operators in visual block mode extend to end of line
1520
                for (var i = 0; i < ranges.length; i++) {
1521
                  ranges[i].head.ch = lineLength(cm, ranges[i].head.line);
1522
                }
1523
              } else if (mode == 'line') {
1524
                ranges[0].head = Pos(ranges[0].head.line + 1, 0);
1525
              }
1526
            }
1527
          } else {
1528
            // Init motion op
1529
            curStart = copyCursor(newAnchor || oldAnchor);
1530
            curEnd = copyCursor(newHead || oldHead);
1531
            if (cursorIsBefore(curEnd, curStart)) {
1532
              var tmp = curStart;
1533
              curStart = curEnd;
1534
              curEnd = tmp;
1535
            }
1536
            linewise = motionArgs.linewise || operatorArgs.linewise;
1537
            if (linewise) {
1538
              // Expand selection to entire line.
1539
              expandSelectionToLine(cm, curStart, curEnd);
1540
            } else if (motionArgs.forward) {
1541
              // Clip to trailing newlines only if the motion goes forward.
1542
              clipToLine(cm, curStart, curEnd);
1543
            }
1544
            mode = 'char';
1545
            var exclusive = !motionArgs.inclusive || linewise;
1546
            cmSel = makeCmSelection(cm, {
1547
              anchor: curStart,
1548
              head: curEnd
1549
            }, mode, exclusive);
1550
          }
1551
          cm.setSelections(cmSel.ranges, cmSel.primary);
1552
          vim.lastMotion = null;
1553
          operatorArgs.repeat = repeat; // For indent in visual mode.
1554
          operatorArgs.registerName = registerName;
1555
          // Keep track of linewise as it affects how paste and change behave.
1556
          operatorArgs.linewise = linewise;
1557
          var operatorMoveTo = operators[operator](
1558
            cm, operatorArgs, cmSel.ranges, oldAnchor, newHead);
0 ignored issues
show
Bug introduced by
The variable newHead does not seem to be initialized in case motion on line 1416 is false. Are you sure the function operator handles undefined variables?
Loading history...
1559
          if (vim.visualMode) {
1560
            exitVisualMode(cm, operatorMoveTo != null);
0 ignored issues
show
Best Practice introduced by
Comparing operatorMoveTo to null using the != operator is not safe. Consider using !== instead.
Loading history...
1561
          }
1562
          if (operatorMoveTo) {
1563
            cm.setCursor(operatorMoveTo);
1564
          }
1565
        }
1566
      },
1567
      recordLastEdit: function(vim, inputState, actionCommand) {
1568
        var macroModeState = vimGlobalState.macroModeState;
1569
        if (macroModeState.isPlaying) { return; }
1570
        vim.lastEditInputState = inputState;
1571
        vim.lastEditActionCommand = actionCommand;
1572
        macroModeState.lastInsertModeChanges.changes = [];
1573
        macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
1574
      }
1575
    };
1576
1577
    /**
1578
     * typedef {Object{line:number,ch:number}} Cursor An object containing the
1579
     *     position of the cursor.
1580
     */
1581
    // All of the functions below return Cursor objects.
1582
    var motions = {
1583
      moveToTopLine: function(cm, _head, motionArgs) {
1584
        var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
1585
        return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
1586
      },
1587
      moveToMiddleLine: function(cm) {
1588
        var range = getUserVisibleLines(cm);
1589
        var line = Math.floor((range.top + range.bottom) * 0.5);
1590
        return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
1591
      },
1592
      moveToBottomLine: function(cm, _head, motionArgs) {
1593
        var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
1594
        return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
1595
      },
1596
      expandToLine: function(_cm, head, motionArgs) {
1597
        // Expands forward to end of line, and then to next line if repeat is
1598
        // >1. Does not handle backward motion!
1599
        var cur = head;
1600
        return Pos(cur.line + motionArgs.repeat - 1, Infinity);
1601
      },
1602
      findNext: function(cm, _head, motionArgs) {
1603
        var state = getSearchState(cm);
1604
        var query = state.getQuery();
1605
        if (!query) {
1606
          return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
1607
        }
1608
        var prev = !motionArgs.forward;
1609
        // If search is initiated with ? instead of /, negate direction.
1610
        prev = (state.isReversed()) ? !prev : prev;
1611
        highlightSearchMatches(cm, query);
1612
        return findNext(cm, prev/** prev */, query, motionArgs.repeat);
1613
      },
1614
      goToMark: function(cm, _head, motionArgs, vim) {
1615
        var mark = vim.marks[motionArgs.selectedCharacter];
1616
        if (mark) {
1617
          var pos = mark.find();
1618
          return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos;
1619
        }
1620
        return null;
1621
      },
1622
      moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) {
1623
        if (vim.visualBlock && motionArgs.sameLine) {
1624
          var sel = vim.sel;
1625
          return [
1626
            clipCursorToContent(cm, Pos(sel.anchor.line, sel.head.ch)),
1627
            clipCursorToContent(cm, Pos(sel.head.line, sel.anchor.ch))
1628
          ];
1629
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1630
          return ([vim.sel.head, vim.sel.anchor]);
1631
        }
1632
      },
1633
      jumpToMark: function(cm, head, motionArgs, vim) {
1634
        var best = head;
1635
        for (var i = 0; i < motionArgs.repeat; i++) {
1636
          var cursor = best;
1637
          for (var key in vim.marks) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
1638
            if (!isLowerCase(key)) {
1639
              continue;
1640
            }
1641
            var mark = vim.marks[key].find();
1642
            var isWrongDirection = (motionArgs.forward) ?
1643
              cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);
1644
1645
            if (isWrongDirection) {
1646
              continue;
1647
            }
1648
            if (motionArgs.linewise && (mark.line == cursor.line)) {
1649
              continue;
1650
            }
1651
1652
            var equal = cursorEqual(cursor, best);
1653
            var between = (motionArgs.forward) ?
1654
              cursorIsBetween(cursor, mark, best) :
1655
              cursorIsBetween(best, mark, cursor);
1656
1657
            if (equal || between) {
1658
              best = mark;
1659
            }
1660
          }
1661
        }
1662
1663
        if (motionArgs.linewise) {
1664
          // Vim places the cursor on the first non-whitespace character of
1665
          // the line if there is one, else it places the cursor at the end
1666
          // of the line, regardless of whether a mark was found.
1667
          best = Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line)));
1668
        }
1669
        return best;
1670
      },
1671
      moveByCharacters: function(_cm, head, motionArgs) {
1672
        var cur = head;
1673
        var repeat = motionArgs.repeat;
1674
        var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
1675
        return Pos(cur.line, ch);
1676
      },
1677
      moveByLines: function(cm, head, motionArgs, vim) {
1678
        var cur = head;
1679
        var endCh = cur.ch;
1680
        // Depending what our last motion was, we may want to do different
1681
        // things. If our last motion was moving vertically, we want to
1682
        // preserve the HPos from our last horizontal move.  If our last motion
1683
        // was going to the end of a line, moving vertically we should go to
1684
        // the end of the line, etc.
1685
        switch (vim.lastMotion) {
1686
          case this.moveByLines:
1687
          case this.moveByDisplayLines:
1688
          case this.moveByScroll:
1689
          case this.moveToColumn:
1690
          case this.moveToEol:
1691
            endCh = vim.lastHPos;
1692
            break;
1693
          default:
1694
            vim.lastHPos = endCh;
1695
        }
1696
        var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
1697
        var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
1698
        var first = cm.firstLine();
1699
        var last = cm.lastLine();
1700
        // Vim cancels linewise motions that start on an edge and move beyond
1701
        // that edge. It does not cancel motions that do not start on an edge.
1702
        if ((line < first && cur.line == first) ||
1703
            (line > last && cur.line == last)) {
1704
          return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
1705
        }
1706
        if (motionArgs.toFirstChar){
1707
          endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
1708
          vim.lastHPos = endCh;
1709
        }
1710
        vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left;
1711
        return Pos(line, endCh);
1712
      },
1713
      moveByDisplayLines: function(cm, head, motionArgs, vim) {
1714
        var cur = head;
1715
        switch (vim.lastMotion) {
1716
          case this.moveByDisplayLines:
1717
          case this.moveByScroll:
1718
          case this.moveByLines:
1719
          case this.moveToColumn:
1720
          case this.moveToEol:
1721
            break;
1722
          default:
1723
            vim.lastHSPos = cm.charCoords(cur,'div').left;
1724
        }
1725
        var repeat = motionArgs.repeat;
1726
        var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);
1727
        if (res.hitSide) {
1728
          if (motionArgs.forward) {
1729
            var lastCharCoords = cm.charCoords(res, 'div');
1730
            var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };
1731
            var res = cm.coordsChar(goalCoords, 'div');
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable res already seems to be declared on line 1726. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1732
          } else {
1733
            var resCoords = cm.charCoords(Pos(cm.firstLine(), 0), 'div');
1734
            resCoords.left = vim.lastHSPos;
1735
            res = cm.coordsChar(resCoords, 'div');
1736
          }
1737
        }
1738
        vim.lastHPos = res.ch;
1739
        return res;
1740
      },
1741
      moveByPage: function(cm, head, motionArgs) {
1742
        // CodeMirror only exposes functions that move the cursor page down, so
1743
        // doing this bad hack to move the cursor and move it back. evalInput
1744
        // will move the cursor to where it should be in the end.
1745
        var curStart = head;
1746
        var repeat = motionArgs.repeat;
1747
        return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page');
1748
      },
1749
      moveByParagraph: function(cm, head, motionArgs) {
1750
        var dir = motionArgs.forward ? 1 : -1;
1751
        return findParagraph(cm, head, motionArgs.repeat, dir);
1752
      },
1753
      moveByScroll: function(cm, head, motionArgs, vim) {
1754
        var scrollbox = cm.getScrollInfo();
1755
        var curEnd = null;
0 ignored issues
show
Unused Code introduced by
The assignment to curEnd seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
1756
        var repeat = motionArgs.repeat;
1757
        if (!repeat) {
1758
          repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
1759
        }
1760
        var orig = cm.charCoords(head, 'local');
1761
        motionArgs.repeat = repeat;
1762
        var curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable curEnd already seems to be declared on line 1755. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1763
        if (!curEnd) {
1764
          return null;
1765
        }
1766
        var dest = cm.charCoords(curEnd, 'local');
1767
        cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
1768
        return curEnd;
1769
      },
1770
      moveByWords: function(cm, head, motionArgs) {
1771
        return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward,
1772
            !!motionArgs.wordEnd, !!motionArgs.bigWord);
1773
      },
1774
      moveTillCharacter: function(cm, _head, motionArgs) {
1775
        var repeat = motionArgs.repeat;
1776
        var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
1777
            motionArgs.selectedCharacter);
1778
        var increment = motionArgs.forward ? -1 : 1;
1779
        recordLastCharacterSearch(increment, motionArgs);
1780
        if (!curEnd) return null;
1781
        curEnd.ch += increment;
1782
        return curEnd;
1783
      },
1784
      moveToCharacter: function(cm, head, motionArgs) {
1785
        var repeat = motionArgs.repeat;
1786
        recordLastCharacterSearch(0, motionArgs);
1787
        return moveToCharacter(cm, repeat, motionArgs.forward,
1788
            motionArgs.selectedCharacter) || head;
1789
      },
1790
      moveToSymbol: function(cm, head, motionArgs) {
1791
        var repeat = motionArgs.repeat;
1792
        return findSymbol(cm, repeat, motionArgs.forward,
1793
            motionArgs.selectedCharacter) || head;
1794
      },
1795
      moveToColumn: function(cm, head, motionArgs, vim) {
1796
        var repeat = motionArgs.repeat;
1797
        // repeat is equivalent to which column we want to move to!
1798
        vim.lastHPos = repeat - 1;
1799
        vim.lastHSPos = cm.charCoords(head,'div').left;
1800
        return moveToColumn(cm, repeat);
1801
      },
1802
      moveToEol: function(cm, head, motionArgs, vim) {
1803
        var cur = head;
1804
        vim.lastHPos = Infinity;
1805
        var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity);
1806
        var end=cm.clipPos(retval);
1807
        end.ch--;
1808
        vim.lastHSPos = cm.charCoords(end,'div').left;
1809
        return retval;
1810
      },
1811
      moveToFirstNonWhiteSpaceCharacter: function(cm, head) {
1812
        // Go to the start of the line where the text begins, or the end for
1813
        // whitespace-only lines
1814
        var cursor = head;
1815
        return Pos(cursor.line,
1816
                   findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));
1817
      },
1818
      moveToMatchedSymbol: function(cm, head) {
1819
        var cursor = head;
1820
        var line = cursor.line;
1821
        var ch = cursor.ch;
1822
        var lineText = cm.getLine(line);
1823
        var symbol;
1824
        do {
1825
          symbol = lineText.charAt(ch++);
1826
          if (symbol && isMatchableSymbol(symbol)) {
1827
            var style = cm.getTokenTypeAt(Pos(line, ch));
1828
            if (style !== "string" && style !== "comment") {
1829
              break;
1830
            }
1831
          }
1832
        } while (symbol);
1833
        if (symbol) {
1834
          var matched = cm.findMatchingBracket(Pos(line, ch));
1835
          return matched.to;
1836
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1837
          return cursor;
1838
        }
1839
      },
1840
      moveToStartOfLine: function(_cm, head) {
1841
        return Pos(head.line, 0);
1842
      },
1843
      moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) {
1844
        var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
1845
        if (motionArgs.repeatIsExplicit) {
1846
          lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
1847
        }
1848
        return Pos(lineNum,
1849
                   findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)));
1850
      },
1851
      textObjectManipulation: function(cm, head, motionArgs, vim) {
1852
        // TODO: lots of possible exceptions that can be thrown here. Try da(
1853
        //     outside of a () block.
1854
1855
        // TODO: adding <> >< to this map doesn't work, presumably because
1856
        // they're operators
1857
        var mirroredPairs = {'(': ')', ')': '(',
1858
                             '{': '}', '}': '{',
1859
                             '[': ']', ']': '['};
1860
        var selfPaired = {'\'': true, '"': true};
1861
1862
        var character = motionArgs.selectedCharacter;
1863
        // 'b' refers to  '()' block.
1864
        // 'B' refers to  '{}' block.
1865
        if (character == 'b') {
1866
          character = '(';
1867
        } else if (character == 'B') {
1868
          character = '{';
1869
        }
1870
1871
        // Inclusive is the difference between a and i
1872
        // TODO: Instead of using the additional text object map to perform text
1873
        //     object operations, merge the map into the defaultKeyMap and use
1874
        //     motionArgs to define behavior. Define separate entries for 'aw',
1875
        //     'iw', 'a[', 'i[', etc.
1876
        var inclusive = !motionArgs.textObjectInner;
1877
1878
        var tmp;
1879
        if (mirroredPairs[character]) {
1880
          tmp = selectCompanionObject(cm, head, character, inclusive);
1881
        } else if (selfPaired[character]) {
1882
          tmp = findBeginningAndEnd(cm, head, character, inclusive);
1883
        } else if (character === 'W') {
1884
          tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
1885
                                                     true /** bigWord */);
1886
        } else if (character === 'w') {
1887
          tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
1888
                                                     false /** bigWord */);
1889
        } else if (character === 'p') {
1890
          tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive);
1891
          motionArgs.linewise = true;
1892
          if (vim.visualMode) {
1893
            if (!vim.visualLine) { vim.visualLine = true; }
1894
          } else {
1895
            var operatorArgs = vim.inputState.operatorArgs;
1896
            if (operatorArgs) { operatorArgs.linewise = true; }
1897
            tmp.end.line--;
1898
          }
1899
        } else {
1900
          // No text object defined for this, don't move.
1901
          return null;
1902
        }
1903
1904
        if (!cm.state.vim.visualMode) {
1905
          return [tmp.start, tmp.end];
1906
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1907
          return expandSelection(cm, tmp.start, tmp.end);
1908
        }
1909
      },
1910
1911
      repeatLastCharacterSearch: function(cm, head, motionArgs) {
1912
        var lastSearch = vimGlobalState.lastChararacterSearch;
1913
        var repeat = motionArgs.repeat;
1914
        var forward = motionArgs.forward === lastSearch.forward;
1915
        var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
1916
        cm.moveH(-increment, 'char');
1917
        motionArgs.inclusive = forward ? true : false;
1918
        var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
1919
        if (!curEnd) {
1920
          cm.moveH(increment, 'char');
1921
          return head;
1922
        }
1923
        curEnd.ch += increment;
1924
        return curEnd;
1925
      }
1926
    };
1927
1928
    function defineMotion(name, fn) {
1929
      motions[name] = fn;
1930
    }
1931
1932
    function fillArray(val, times) {
1933
      var arr = [];
1934
      for (var i = 0; i < times; i++) {
1935
        arr.push(val);
1936
      }
1937
      return arr;
1938
    }
1939
    /**
1940
     * An operator acts on a text selection. It receives the list of selections
1941
     * as input. The corresponding CodeMirror selection is guaranteed to
1942
    * match the input selection.
1943
     */
1944
    var operators = {
1945
      change: function(cm, args, ranges) {
1946
        var finalHead, text;
1947
        var vim = cm.state.vim;
1948
        vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock = vim.visualBlock;
1949
        if (!vim.visualMode) {
1950
          var anchor = ranges[0].anchor,
1951
              head = ranges[0].head;
1952
          text = cm.getRange(anchor, head);
1953
          var lastState = vim.lastEditInputState || {};
1954
          if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) {
1955
            // Exclude trailing whitespace if the range is not all whitespace.
1956
            var match = (/\s+$/).exec(text);
1957
            if (match && lastState.motionArgs && lastState.motionArgs.forward) {
1958
              head = offsetCursor(head, 0, - match[0].length);
1959
              text = text.slice(0, - match[0].length);
1960
            }
1961
          }
1962
          var wasLastLine = head.line - 1 == cm.lastLine();
1963
          cm.replaceRange('', anchor, head);
1964
          if (args.linewise && !wasLastLine) {
1965
            // Push the next line back down, if there is a next line.
1966
            CodeMirror.commands.newlineAndIndent(cm);
1967
            // null ch so setCursor moves to end of line.
1968
            anchor.ch = null;
1969
          }
1970
          finalHead = anchor;
1971
        } else {
1972
          text = cm.getSelection();
1973
          var replacement = fillArray('', ranges.length);
1974
          cm.replaceSelections(replacement);
1975
          finalHead = cursorMin(ranges[0].head, ranges[0].anchor);
1976
        }
1977
        vimGlobalState.registerController.pushText(
1978
            args.registerName, 'change', text,
1979
            args.linewise, ranges.length > 1);
1980
        actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim);
1981
      },
1982
      // delete is a javascript keyword.
1983
      'delete': function(cm, args, ranges) {
1984
        var finalHead, text;
1985
        var vim = cm.state.vim;
1986
        if (!vim.visualBlock) {
1987
          var anchor = ranges[0].anchor,
1988
              head = ranges[0].head;
1989
          if (args.linewise &&
1990
              head.line != cm.firstLine() &&
1991
              anchor.line == cm.lastLine() &&
1992
              anchor.line == head.line - 1) {
1993
            // Special case for dd on last line (and first line).
1994
            if (anchor.line == cm.firstLine()) {
1995
              anchor.ch = 0;
1996
            } else {
1997
              anchor = Pos(anchor.line - 1, lineLength(cm, anchor.line - 1));
1998
            }
1999
          }
2000
          text = cm.getRange(anchor, head);
2001
          cm.replaceRange('', anchor, head);
2002
          finalHead = anchor;
2003
          if (args.linewise) {
2004
            finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor);
2005
          }
2006
        } else {
2007
          text = cm.getSelection();
2008
          var replacement = fillArray('', ranges.length);
2009
          cm.replaceSelections(replacement);
2010
          finalHead = ranges[0].anchor;
2011
        }
2012
        vimGlobalState.registerController.pushText(
2013
            args.registerName, 'delete', text,
2014
            args.linewise, vim.visualBlock);
2015
        return clipCursorToContent(cm, finalHead);
2016
      },
2017
      indent: function(cm, args, ranges) {
2018
        var vim = cm.state.vim;
2019
        var startLine = ranges[0].anchor.line;
2020
        var endLine = vim.visualBlock ?
2021
          ranges[ranges.length - 1].anchor.line :
2022
          ranges[0].head.line;
2023
        // In visual mode, n> shifts the selection right n times, instead of
2024
        // shifting n lines right once.
2025
        var repeat = (vim.visualMode) ? args.repeat : 1;
2026
        if (args.linewise) {
2027
          // The only way to delete a newline is to delete until the start of
2028
          // the next line, so in linewise mode evalInput will include the next
2029
          // line. We don't want this in indent, so we go back a line.
2030
          endLine--;
2031
        }
2032
        for (var i = startLine; i <= endLine; i++) {
2033
          for (var j = 0; j < repeat; j++) {
2034
            cm.indentLine(i, args.indentRight);
2035
          }
2036
        }
2037
        return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
2038
      },
2039
      changeCase: function(cm, args, ranges, oldAnchor, newHead) {
2040
        var selections = cm.getSelections();
2041
        var swapped = [];
2042
        var toLower = args.toLower;
2043
        for (var j = 0; j < selections.length; j++) {
2044
          var toSwap = selections[j];
2045
          var text = '';
2046
          if (toLower === true) {
2047
            text = toSwap.toLowerCase();
2048
          } else if (toLower === false) {
2049
            text = toSwap.toUpperCase();
2050
          } else {
2051
            for (var i = 0; i < toSwap.length; i++) {
2052
              var character = toSwap.charAt(i);
2053
              text += isUpperCase(character) ? character.toLowerCase() :
2054
                  character.toUpperCase();
2055
            }
2056
          }
2057
          swapped.push(text);
2058
        }
2059
        cm.replaceSelections(swapped);
2060
        if (args.shouldMoveCursor){
2061
          return newHead;
2062
        } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) {
2063
          return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor);
2064
        } else if (args.linewise){
2065
          return oldAnchor;
2066
        } else {
2067
          return cursorMin(ranges[0].anchor, ranges[0].head);
2068
        }
2069
      },
2070
      yank: function(cm, args, ranges, oldAnchor) {
2071
        var vim = cm.state.vim;
2072
        var text = cm.getSelection();
2073
        var endPos = vim.visualMode
2074
          ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor)
2075
          : oldAnchor;
2076
        vimGlobalState.registerController.pushText(
2077
            args.registerName, 'yank',
2078
            text, args.linewise, vim.visualBlock);
2079
        return endPos;
2080
      }
2081
    };
2082
2083
    function defineOperator(name, fn) {
2084
      operators[name] = fn;
2085
    }
2086
2087
    var actions = {
2088
      jumpListWalk: function(cm, actionArgs, vim) {
2089
        if (vim.visualMode) {
2090
          return;
2091
        }
2092
        var repeat = actionArgs.repeat;
2093
        var forward = actionArgs.forward;
2094
        var jumpList = vimGlobalState.jumpList;
2095
2096
        var mark = jumpList.move(cm, forward ? repeat : -repeat);
2097
        var markPos = mark ? mark.find() : undefined;
2098
        markPos = markPos ? markPos : cm.getCursor();
2099
        cm.setCursor(markPos);
2100
      },
2101
      scroll: function(cm, actionArgs, vim) {
2102
        if (vim.visualMode) {
2103
          return;
2104
        }
2105
        var repeat = actionArgs.repeat || 1;
2106
        var lineHeight = cm.defaultTextHeight();
2107
        var top = cm.getScrollInfo().top;
2108
        var delta = lineHeight * repeat;
2109
        var newPos = actionArgs.forward ? top + delta : top - delta;
2110
        var cursor = copyCursor(cm.getCursor());
2111
        var cursorCoords = cm.charCoords(cursor, 'local');
2112
        if (actionArgs.forward) {
2113
          if (newPos > cursorCoords.top) {
2114
             cursor.line += (newPos - cursorCoords.top) / lineHeight;
2115
             cursor.line = Math.ceil(cursor.line);
2116
             cm.setCursor(cursor);
2117
             cursorCoords = cm.charCoords(cursor, 'local');
2118
             cm.scrollTo(null, cursorCoords.top);
2119
          } else {
2120
             // Cursor stays within bounds.  Just reposition the scroll window.
2121
             cm.scrollTo(null, newPos);
2122
          }
2123
        } else {
2124
          var newBottom = newPos + cm.getScrollInfo().clientHeight;
2125
          if (newBottom < cursorCoords.bottom) {
2126
             cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight;
2127
             cursor.line = Math.floor(cursor.line);
2128
             cm.setCursor(cursor);
2129
             cursorCoords = cm.charCoords(cursor, 'local');
2130
             cm.scrollTo(
2131
                 null, cursorCoords.bottom - cm.getScrollInfo().clientHeight);
2132
          } else {
2133
             // Cursor stays within bounds.  Just reposition the scroll window.
2134
             cm.scrollTo(null, newPos);
2135
          }
2136
        }
2137
      },
2138
      scrollToCursor: function(cm, actionArgs) {
2139
        var lineNum = cm.getCursor().line;
2140
        var charCoords = cm.charCoords(Pos(lineNum, 0), 'local');
2141
        var height = cm.getScrollInfo().clientHeight;
2142
        var y = charCoords.top;
2143
        var lineHeight = charCoords.bottom - y;
2144
        switch (actionArgs.position) {
2145
          case 'center': y = y - (height / 2) + lineHeight;
2146
            break;
2147
          case 'bottom': y = y - height + lineHeight*1.4;
2148
            break;
2149
          case 'top': y = y + lineHeight*0.4;
2150
            break;
2151
        }
2152
        cm.scrollTo(null, y);
2153
      },
2154
      replayMacro: function(cm, actionArgs, vim) {
2155
        var registerName = actionArgs.selectedCharacter;
2156
        var repeat = actionArgs.repeat;
2157
        var macroModeState = vimGlobalState.macroModeState;
2158
        if (registerName == '@') {
2159
          registerName = macroModeState.latestRegister;
2160
        }
2161
        while(repeat--){
2162
          executeMacroRegister(cm, vim, macroModeState, registerName);
2163
        }
2164
      },
2165
      enterMacroRecordMode: function(cm, actionArgs) {
2166
        var macroModeState = vimGlobalState.macroModeState;
2167
        var registerName = actionArgs.selectedCharacter;
2168
        macroModeState.enterMacroRecordMode(cm, registerName);
2169
      },
2170
      enterInsertMode: function(cm, actionArgs, vim) {
2171
        if (cm.getOption('readOnly')) { return; }
2172
        vim.insertMode = true;
2173
        vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
2174
        var insertAt = (actionArgs) ? actionArgs.insertAt : null;
2175
        var sel = vim.sel;
2176
        var head = actionArgs.head || cm.getCursor('head');
2177
        var height = cm.listSelections().length;
2178
        if (insertAt == 'eol') {
2179
          head = Pos(head.line, lineLength(cm, head.line));
2180
        } else if (insertAt == 'charAfter') {
2181
          head = offsetCursor(head, 0, 1);
2182
        } else if (insertAt == 'firstNonBlank') {
2183
          head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head);
2184
        } else if (insertAt == 'startOfSelectedArea') {
2185
          if (!vim.visualBlock) {
2186
            if (sel.head.line < sel.anchor.line) {
2187
              head = sel.head;
2188
            } else {
2189
              head = Pos(sel.anchor.line, 0);
2190
            }
2191
          } else {
2192
            head = Pos(
2193
                Math.min(sel.head.line, sel.anchor.line),
2194
                Math.min(sel.head.ch, sel.anchor.ch));
2195
            height = Math.abs(sel.head.line - sel.anchor.line) + 1;
2196
          }
2197
        } else if (insertAt == 'endOfSelectedArea') {
2198
          if (!vim.visualBlock) {
2199
            if (sel.head.line >= sel.anchor.line) {
2200
              head = offsetCursor(sel.head, 0, 1);
2201
            } else {
2202
              head = Pos(sel.anchor.line, 0);
2203
            }
2204
          } else {
2205
            head = Pos(
2206
                Math.min(sel.head.line, sel.anchor.line),
2207
                Math.max(sel.head.ch + 1, sel.anchor.ch));
2208
            height = Math.abs(sel.head.line - sel.anchor.line) + 1;
2209
          }
2210
        } else if (insertAt == 'inplace') {
2211
          if (vim.visualMode){
2212
            return;
2213
          }
2214
        }
2215
        cm.setOption('keyMap', 'vim-insert');
2216
        cm.setOption('disableInput', false);
2217
        if (actionArgs && actionArgs.replace) {
2218
          // Handle Replace-mode as a special case of insert mode.
2219
          cm.toggleOverwrite(true);
2220
          cm.setOption('keyMap', 'vim-replace');
2221
          CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
2222
        } else {
2223
          cm.setOption('keyMap', 'vim-insert');
2224
          CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
2225
        }
2226
        if (!vimGlobalState.macroModeState.isPlaying) {
2227
          // Only record if not replaying.
2228
          cm.on('change', onChange);
2229
          CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
2230
        }
2231
        if (vim.visualMode) {
2232
          exitVisualMode(cm);
2233
        }
2234
        selectForInsert(cm, head, height);
2235
      },
2236
      toggleVisualMode: function(cm, actionArgs, vim) {
2237
        var repeat = actionArgs.repeat;
2238
        var anchor = cm.getCursor();
2239
        var head;
2240
        // TODO: The repeat should actually select number of characters/lines
2241
        //     equal to the repeat times the size of the previous visual
2242
        //     operation.
2243
        if (!vim.visualMode) {
2244
          // Entering visual mode
2245
          vim.visualMode = true;
2246
          vim.visualLine = !!actionArgs.linewise;
2247
          vim.visualBlock = !!actionArgs.blockwise;
2248
          head = clipCursorToContent(
2249
              cm, Pos(anchor.line, anchor.ch + repeat - 1),
2250
              true /** includeLineBreak */);
2251
          vim.sel = {
2252
            anchor: anchor,
2253
            head: head
2254
          };
2255
          CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
2256
          updateCmSelection(cm);
2257
          updateMark(cm, vim, '<', cursorMin(anchor, head));
2258
          updateMark(cm, vim, '>', cursorMax(anchor, head));
2259
        } else if (vim.visualLine ^ actionArgs.linewise ||
2260
            vim.visualBlock ^ actionArgs.blockwise) {
2261
          // Toggling between modes
2262
          vim.visualLine = !!actionArgs.linewise;
2263
          vim.visualBlock = !!actionArgs.blockwise;
2264
          CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
2265
          updateCmSelection(cm);
2266
        } else {
2267
          exitVisualMode(cm);
2268
        }
2269
      },
2270
      reselectLastSelection: function(cm, _actionArgs, vim) {
2271
        var lastSelection = vim.lastSelection;
2272
        if (vim.visualMode) {
2273
          updateLastSelection(cm, vim);
2274
        }
2275
        if (lastSelection) {
2276
          var anchor = lastSelection.anchorMark.find();
2277
          var head = lastSelection.headMark.find();
2278
          if (!anchor || !head) {
2279
            // If the marks have been destroyed due to edits, do nothing.
2280
            return;
2281
          }
2282
          vim.sel = {
2283
            anchor: anchor,
2284
            head: head
2285
          };
2286
          vim.visualMode = true;
2287
          vim.visualLine = lastSelection.visualLine;
2288
          vim.visualBlock = lastSelection.visualBlock;
2289
          updateCmSelection(cm);
2290
          updateMark(cm, vim, '<', cursorMin(anchor, head));
2291
          updateMark(cm, vim, '>', cursorMax(anchor, head));
2292
          CodeMirror.signal(cm, 'vim-mode-change', {
2293
            mode: 'visual',
2294
            subMode: vim.visualLine ? 'linewise' :
2295
                     vim.visualBlock ? 'blockwise' : ''});
2296
        }
2297
      },
2298
      joinLines: function(cm, actionArgs, vim) {
2299
        var curStart, curEnd;
2300
        if (vim.visualMode) {
2301
          curStart = cm.getCursor('anchor');
2302
          curEnd = cm.getCursor('head');
2303
          if (cursorIsBefore(curEnd, curStart)) {
2304
            var tmp = curEnd;
2305
            curEnd = curStart;
2306
            curStart = tmp;
2307
          }
2308
          curEnd.ch = lineLength(cm, curEnd.line) - 1;
2309
        } else {
2310
          // Repeat is the number of lines to join. Minimum 2 lines.
2311
          var repeat = Math.max(actionArgs.repeat, 2);
2312
          curStart = cm.getCursor();
2313
          curEnd = clipCursorToContent(cm, Pos(curStart.line + repeat - 1,
2314
                                               Infinity));
2315
        }
2316
        var finalCh = 0;
2317
        for (var i = curStart.line; i < curEnd.line; i++) {
2318
          finalCh = lineLength(cm, curStart.line);
2319
          var tmp = Pos(curStart.line + 1,
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable tmp already seems to be declared on line 2304. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
2320
                        lineLength(cm, curStart.line + 1));
2321
          var text = cm.getRange(curStart, tmp);
2322
          text = text.replace(/\n\s*/g, ' ');
2323
          cm.replaceRange(text, curStart, tmp);
2324
        }
2325
        var curFinalPos = Pos(curStart.line, finalCh);
2326
        if (vim.visualMode) {
2327
          exitVisualMode(cm, false);
2328
        }
2329
        cm.setCursor(curFinalPos);
2330
      },
2331
      newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
2332
        vim.insertMode = true;
2333
        var insertAt = copyCursor(cm.getCursor());
2334
        if (insertAt.line === cm.firstLine() && !actionArgs.after) {
2335
          // Special case for inserting newline before start of document.
2336
          cm.replaceRange('\n', Pos(cm.firstLine(), 0));
2337
          cm.setCursor(cm.firstLine(), 0);
2338
        } else {
2339
          insertAt.line = (actionArgs.after) ? insertAt.line :
2340
              insertAt.line - 1;
2341
          insertAt.ch = lineLength(cm, insertAt.line);
2342
          cm.setCursor(insertAt);
2343
          var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||
2344
              CodeMirror.commands.newlineAndIndent;
2345
          newlineFn(cm);
2346
        }
2347
        this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);
2348
      },
2349
      paste: function(cm, actionArgs, vim) {
2350
        var cur = copyCursor(cm.getCursor());
2351
        var register = vimGlobalState.registerController.getRegister(
2352
            actionArgs.registerName);
2353
        var text = register.toString();
2354
        if (!text) {
2355
          return;
2356
        }
2357
        if (actionArgs.matchIndent) {
2358
          var tabSize = cm.getOption("tabSize");
2359
          // length that considers tabs and tabSize
2360
          var whitespaceLength = function(str) {
2361
            var tabs = (str.split("\t").length - 1);
2362
            var spaces = (str.split(" ").length - 1);
2363
            return tabs * tabSize + spaces * 1;
2364
          };
2365
          var currentLine = cm.getLine(cm.getCursor().line);
2366
          var indent = whitespaceLength(currentLine.match(/^\s*/)[0]);
2367
          // chomp last newline b/c don't want it to match /^\s*/gm
2368
          var chompedText = text.replace(/\n$/, '');
2369
          var wasChomped = text !== chompedText;
2370
          var firstIndent = whitespaceLength(text.match(/^\s*/)[0]);
2371
          var text = chompedText.replace(/^\s*/gm, function(wspace) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable text already seems to be declared on line 2353. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
2372
            var newIndent = indent + (whitespaceLength(wspace) - firstIndent);
2373
            if (newIndent < 0) {
2374
              return "";
2375
            }
2376
            else if (cm.getOption("indentWithTabs")) {
2377
              var quotient = Math.floor(newIndent / tabSize);
2378
              return Array(quotient + 1).join('\t');
2379
            }
2380
            else {
2381
              return Array(newIndent + 1).join(' ');
2382
            }
2383
          });
2384
          text += wasChomped ? "\n" : "";
2385
        }
2386
        if (actionArgs.repeat > 1) {
2387
          var text = Array(actionArgs.repeat + 1).join(text);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable text already seems to be declared on line 2353. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
2388
        }
2389
        var linewise = register.linewise;
2390
        var blockwise = register.blockwise;
2391
        if (linewise) {
2392
          if(vim.visualMode) {
2393
            text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n';
2394
          } else if (actionArgs.after) {
2395
            // Move the newline at the end to the start instead, and paste just
2396
            // before the newline character of the line we are on right now.
2397
            text = '\n' + text.slice(0, text.length - 1);
2398
            cur.ch = lineLength(cm, cur.line);
2399
          } else {
2400
            cur.ch = 0;
2401
          }
2402
        } else {
2403
          if (blockwise) {
2404
            text = text.split('\n');
2405
            for (var i = 0; i < text.length; i++) {
2406
              text[i] = (text[i] == '') ? ' ' : text[i];
2407
            }
2408
          }
2409
          cur.ch += actionArgs.after ? 1 : 0;
2410
        }
2411
        var curPosFinal;
2412
        var idx;
2413
        if (vim.visualMode) {
2414
          //  save the pasted text for reselection if the need arises
2415
          vim.lastPastedText = text;
2416
          var lastSelectionCurEnd;
2417
          var selectedArea = getSelectedAreaRange(cm, vim);
2418
          var selectionStart = selectedArea[0];
2419
          var selectionEnd = selectedArea[1];
2420
          var selectedText = cm.getSelection();
2421
          var selections = cm.listSelections();
2422
          var emptyStrings = new Array(selections.length).join('1').split('1');
2423
          // save the curEnd marker before it get cleared due to cm.replaceRange.
2424
          if (vim.lastSelection) {
2425
            lastSelectionCurEnd = vim.lastSelection.headMark.find();
2426
          }
2427
          // push the previously selected text to unnamed register
2428
          vimGlobalState.registerController.unnamedRegister.setText(selectedText);
2429
          if (blockwise) {
2430
            // first delete the selected text
2431
            cm.replaceSelections(emptyStrings);
2432
            // Set new selections as per the block length of the yanked text
2433
            selectionEnd = Pos(selectionStart.line + text.length-1, selectionStart.ch);
2434
            cm.setCursor(selectionStart);
2435
            selectBlock(cm, selectionEnd);
2436
            cm.replaceSelections(text);
2437
            curPosFinal = selectionStart;
2438
          } else if (vim.visualBlock) {
2439
            cm.replaceSelections(emptyStrings);
2440
            cm.setCursor(selectionStart);
2441
            cm.replaceRange(text, selectionStart, selectionStart);
2442
            curPosFinal = selectionStart;
2443
          } else {
2444
            cm.replaceRange(text, selectionStart, selectionEnd);
2445
            curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1);
2446
          }
2447
          // restore the the curEnd marker
2448
          if(lastSelectionCurEnd) {
2449
            vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd);
2450
          }
2451
          if (linewise) {
2452
            curPosFinal.ch=0;
2453
          }
2454
        } else {
2455
          if (blockwise) {
2456
            cm.setCursor(cur);
2457
            for (var i = 0; i < text.length; i++) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 2405. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
2458
              var line = cur.line+i;
2459
              if (line > cm.lastLine()) {
2460
                cm.replaceRange('\n',  Pos(line, 0));
2461
              }
2462
              var lastCh = lineLength(cm, line);
2463
              if (lastCh < cur.ch) {
2464
                extendLineToColumn(cm, line, cur.ch);
2465
              }
2466
            }
2467
            cm.setCursor(cur);
2468
            selectBlock(cm, Pos(cur.line + text.length-1, cur.ch));
2469
            cm.replaceSelections(text);
2470
            curPosFinal = cur;
2471
          } else {
2472
            cm.replaceRange(text, cur);
2473
            // Now fine tune the cursor to where we want it.
2474
            if (linewise && actionArgs.after) {
2475
              curPosFinal = Pos(
2476
              cur.line + 1,
2477
              findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)));
2478
            } else if (linewise && !actionArgs.after) {
2479
              curPosFinal = Pos(
2480
                cur.line,
2481
                findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
2482
            } else if (!linewise && actionArgs.after) {
2483
              idx = cm.indexFromPos(cur);
2484
              curPosFinal = cm.posFromIndex(idx + text.length - 1);
2485
            } else {
2486
              idx = cm.indexFromPos(cur);
2487
              curPosFinal = cm.posFromIndex(idx + text.length);
2488
            }
2489
          }
2490
        }
2491
        if (vim.visualMode) {
2492
          exitVisualMode(cm, false);
2493
        }
2494
        cm.setCursor(curPosFinal);
2495
      },
2496
      undo: function(cm, actionArgs) {
2497
        cm.operation(function() {
2498
          repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
2499
          cm.setCursor(cm.getCursor('anchor'));
2500
        });
2501
      },
2502
      redo: function(cm, actionArgs) {
2503
        repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
2504
      },
2505
      setRegister: function(_cm, actionArgs, vim) {
2506
        vim.inputState.registerName = actionArgs.selectedCharacter;
2507
      },
2508
      setMark: function(cm, actionArgs, vim) {
2509
        var markName = actionArgs.selectedCharacter;
2510
        updateMark(cm, vim, markName, cm.getCursor());
2511
      },
2512
      replace: function(cm, actionArgs, vim) {
2513
        var replaceWith = actionArgs.selectedCharacter;
2514
        var curStart = cm.getCursor();
2515
        var replaceTo;
2516
        var curEnd;
2517
        var selections = cm.listSelections();
2518
        if (vim.visualMode) {
2519
          curStart = cm.getCursor('start');
2520
          curEnd = cm.getCursor('end');
2521
        } else {
2522
          var line = cm.getLine(curStart.line);
2523
          replaceTo = curStart.ch + actionArgs.repeat;
2524
          if (replaceTo > line.length) {
2525
            replaceTo=line.length;
2526
          }
2527
          curEnd = Pos(curStart.line, replaceTo);
2528
        }
2529
        if (replaceWith=='\n') {
2530
          if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
2531
          // special case, where vim help says to replace by just one line-break
2532
          (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
2533
        } else {
2534
          var replaceWithStr = cm.getRange(curStart, curEnd);
2535
          //replace all characters in range by selected, but keep linebreaks
2536
          replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith);
2537
          if (vim.visualBlock) {
2538
            // Tabs are split in visua block before replacing
2539
            var spaces = new Array(cm.getOption("tabSize")+1).join(' ');
2540
            replaceWithStr = cm.getSelection();
2541
            replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n');
2542
            cm.replaceSelections(replaceWithStr);
2543
          } else {
2544
            cm.replaceRange(replaceWithStr, curStart, curEnd);
2545
          }
2546
          if (vim.visualMode) {
2547
            curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ?
2548
                         selections[0].anchor : selections[0].head;
2549
            cm.setCursor(curStart);
2550
            exitVisualMode(cm, false);
2551
          } else {
2552
            cm.setCursor(offsetCursor(curEnd, 0, -1));
2553
          }
2554
        }
2555
      },
2556
      incrementNumberToken: function(cm, actionArgs) {
2557
        var cur = cm.getCursor();
2558
        var lineStr = cm.getLine(cur.line);
2559
        var re = /-?\d+/g;
2560
        var match;
2561
        var start;
2562
        var end;
2563
        var numberStr;
2564
        var token;
2565
        while ((match = re.exec(lineStr)) !== null) {
2566
          token = match[0];
2567
          start = match.index;
2568
          end = start + token.length;
2569
          if (cur.ch < end)break;
2570
        }
2571
        if (!actionArgs.backtrack && (end <= cur.ch))return;
0 ignored issues
show
Comprehensibility Bug introduced by
The variable end does not seem to be initialized in case the while loop on line 2565 is not entered. Are you sure this can never be the case?
Loading history...
2572
        if (token) {
2573
          var increment = actionArgs.increase ? 1 : -1;
2574
          var number = parseInt(token) + (increment * actionArgs.repeat);
2575
          var from = Pos(cur.line, start);
0 ignored issues
show
introduced by
The variable start does not seem to be initialized in case the while loop on line 2565 is not entered. Are you sure the function Pos handles undefined variables?
Loading history...
2576
          var to = Pos(cur.line, end);
2577
          numberStr = number.toString();
2578
          cm.replaceRange(numberStr, from, to);
2579
        } else {
2580
          return;
2581
        }
2582
        cm.setCursor(Pos(cur.line, start + numberStr.length - 1));
2583
      },
2584
      repeatLastEdit: function(cm, actionArgs, vim) {
2585
        var lastEditInputState = vim.lastEditInputState;
2586
        if (!lastEditInputState) { return; }
2587
        var repeat = actionArgs.repeat;
2588
        if (repeat && actionArgs.repeatIsExplicit) {
2589
          vim.lastEditInputState.repeatOverride = repeat;
2590
        } else {
2591
          repeat = vim.lastEditInputState.repeatOverride || repeat;
2592
        }
2593
        repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
2594
      },
2595
      exitInsertMode: exitInsertMode
2596
    };
2597
2598
    function defineAction(name, fn) {
2599
      actions[name] = fn;
2600
    }
2601
2602
    /*
2603
     * Below are miscellaneous utility functions used by vim.js
2604
     */
2605
2606
    /**
2607
     * Clips cursor to ensure that line is within the buffer's range
2608
     * If includeLineBreak is true, then allow cur.ch == lineLength.
2609
     */
2610
    function clipCursorToContent(cm, cur, includeLineBreak) {
2611
      var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );
2612
      var maxCh = lineLength(cm, line) - 1;
2613
      maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
2614
      var ch = Math.min(Math.max(0, cur.ch), maxCh);
2615
      return Pos(line, ch);
2616
    }
2617
    function copyArgs(args) {
2618
      var ret = {};
2619
      for (var prop in args) {
2620
        if (args.hasOwnProperty(prop)) {
2621
          ret[prop] = args[prop];
2622
        }
2623
      }
2624
      return ret;
2625
    }
2626
    function offsetCursor(cur, offsetLine, offsetCh) {
2627
      if (typeof offsetLine === 'object') {
2628
        offsetCh = offsetLine.ch;
2629
        offsetLine = offsetLine.line;
2630
      }
2631
      return Pos(cur.line + offsetLine, cur.ch + offsetCh);
2632
    }
2633
    function getOffset(anchor, head) {
2634
      return {
2635
        line: head.line - anchor.line,
2636
        ch: head.line - anchor.line
2637
      };
2638
    }
2639
    function commandMatches(keys, keyMap, context, inputState) {
2640
      // Partial matches are not applied. They inform the key handler
2641
      // that the current key sequence is a subsequence of a valid key
2642
      // sequence, so that the key buffer is not cleared.
2643
      var match, partial = [], full = [];
2644
      for (var i = 0; i < keyMap.length; i++) {
2645
        var command = keyMap[i];
2646
        if (context == 'insert' && command.context != 'insert' ||
2647
            command.context && command.context != context ||
2648
            inputState.operator && command.type == 'action' ||
2649
            !(match = commandMatch(keys, command.keys))) { continue; }
2650
        if (match == 'partial') { partial.push(command); }
2651
        if (match == 'full') { full.push(command); }
2652
      }
2653
      return {
2654
        partial: partial.length && partial,
2655
        full: full.length && full
2656
      };
2657
    }
2658
    function commandMatch(pressed, mapped) {
2659
      if (mapped.slice(-11) == '<character>') {
2660
        // Last character matches anything.
2661
        var prefixLen = mapped.length - 11;
2662
        var pressedPrefix = pressed.slice(0, prefixLen);
2663
        var mappedPrefix = mapped.slice(0, prefixLen);
2664
        return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' :
2665
               mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false;
0 ignored issues
show
Best Practice introduced by
Comparing mappedPrefix.indexOf(pressedPrefix) to 0 using the == operator is not safe. Consider using === instead.
Loading history...
2666
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
2667
        return pressed == mapped ? 'full' :
2668
               mapped.indexOf(pressed) == 0 ? 'partial' : false;
0 ignored issues
show
Best Practice introduced by
Comparing mapped.indexOf(pressed) to 0 using the == operator is not safe. Consider using === instead.
Loading history...
2669
      }
2670
    }
2671
    function lastChar(keys) {
2672
      var match = /^.*(<[\w\-]+>)$/.exec(keys);
2673
      var selectedCharacter = match ? match[1] : keys.slice(-1);
2674
      if (selectedCharacter.length > 1){
2675
        switch(selectedCharacter){
2676
          case '<CR>':
2677
            selectedCharacter='\n';
2678
            break;
2679
          case '<Space>':
2680
            selectedCharacter=' ';
2681
            break;
2682
          default:
2683
            break;
2684
        }
2685
      }
2686
      return selectedCharacter;
2687
    }
2688
    function repeatFn(cm, fn, repeat) {
2689
      return function() {
2690
        for (var i = 0; i < repeat; i++) {
2691
          fn(cm);
2692
        }
2693
      };
2694
    }
2695
    function copyCursor(cur) {
2696
      return Pos(cur.line, cur.ch);
2697
    }
2698
    function cursorEqual(cur1, cur2) {
2699
      return cur1.ch == cur2.ch && cur1.line == cur2.line;
2700
    }
2701
    function cursorIsBefore(cur1, cur2) {
2702
      if (cur1.line < cur2.line) {
2703
        return true;
2704
      }
2705
      if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
2706
        return true;
2707
      }
2708
      return false;
2709
    }
2710
    function cursorMin(cur1, cur2) {
2711
      if (arguments.length > 2) {
2712
        cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1));
2713
      }
2714
      return cursorIsBefore(cur1, cur2) ? cur1 : cur2;
2715
    }
2716
    function cursorMax(cur1, cur2) {
2717
      if (arguments.length > 2) {
2718
        cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1));
2719
      }
2720
      return cursorIsBefore(cur1, cur2) ? cur2 : cur1;
2721
    }
2722
    function cursorIsBetween(cur1, cur2, cur3) {
2723
      // returns true if cur2 is between cur1 and cur3.
2724
      var cur1before2 = cursorIsBefore(cur1, cur2);
2725
      var cur2before3 = cursorIsBefore(cur2, cur3);
2726
      return cur1before2 && cur2before3;
2727
    }
2728
    function lineLength(cm, lineNum) {
2729
      return cm.getLine(lineNum).length;
2730
    }
2731
    function trim(s) {
2732
      if (s.trim) {
2733
        return s.trim();
2734
      }
2735
      return s.replace(/^\s+|\s+$/g, '');
2736
    }
2737
    function escapeRegex(s) {
2738
      return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
2739
    }
2740
    function extendLineToColumn(cm, lineNum, column) {
2741
      var endCh = lineLength(cm, lineNum);
2742
      var spaces = new Array(column-endCh+1).join(' ');
2743
      cm.setCursor(Pos(lineNum, endCh));
2744
      cm.replaceRange(spaces, cm.getCursor());
2745
    }
2746
    // This functions selects a rectangular block
2747
    // of text with selectionEnd as any of its corner
2748
    // Height of block:
2749
    // Difference in selectionEnd.line and first/last selection.line
2750
    // Width of the block:
2751
    // Distance between selectionEnd.ch and any(first considered here) selection.ch
2752
    function selectBlock(cm, selectionEnd) {
2753
      var selections = [], ranges = cm.listSelections();
2754
      var head = copyCursor(cm.clipPos(selectionEnd));
2755
      var isClipped = !cursorEqual(selectionEnd, head);
2756
      var curHead = cm.getCursor('head');
2757
      var primIndex = getIndex(ranges, curHead);
2758
      var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor);
2759
      var max = ranges.length - 1;
2760
      var index = max - primIndex > primIndex ? max : 0;
2761
      var base = ranges[index].anchor;
2762
2763
      var firstLine = Math.min(base.line, head.line);
2764
      var lastLine = Math.max(base.line, head.line);
2765
      var baseCh = base.ch, headCh = head.ch;
2766
2767
      var dir = ranges[index].head.ch - baseCh;
2768
      var newDir = headCh - baseCh;
2769 View Code Duplication
      if (dir > 0 && newDir <= 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2770
        baseCh++;
2771
        if (!isClipped) { headCh--; }
2772
      } else if (dir < 0 && newDir >= 0) {
2773
        baseCh--;
2774
        if (!wasClipped) { headCh++; }
2775
      } else if (dir < 0 && newDir == -1) {
2776
        baseCh--;
2777
        headCh++;
2778
      }
2779
      for (var line = firstLine; line <= lastLine; line++) {
2780
        var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)};
2781
        selections.push(range);
2782
      }
2783
      primIndex = head.line == lastLine ? selections.length - 1 : 0;
0 ignored issues
show
Unused Code introduced by
The assignment to variable primIndex seems to be never used. Consider removing it.
Loading history...
2784
      cm.setSelections(selections);
2785
      selectionEnd.ch = headCh;
2786
      base.ch = baseCh;
2787
      return base;
2788
    }
2789
    function selectForInsert(cm, head, height) {
2790
      var sel = [];
2791
      for (var i = 0; i < height; i++) {
2792
        var lineHead = offsetCursor(head, i, 0);
2793
        sel.push({anchor: lineHead, head: lineHead});
2794
      }
2795
      cm.setSelections(sel, 0);
2796
    }
2797
    // getIndex returns the index of the cursor in the selections.
2798
    function getIndex(ranges, cursor, end) {
2799
      for (var i = 0; i < ranges.length; i++) {
2800
        var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor);
2801
        var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor);
2802
        if (atAnchor || atHead) {
2803
          return i;
2804
        }
2805
      }
2806
      return -1;
2807
    }
2808
    function getSelectedAreaRange(cm, vim) {
2809
      var lastSelection = vim.lastSelection;
2810
      var getCurrentSelectedAreaRange = function() {
2811
        var selections = cm.listSelections();
2812
        var start =  selections[0];
2813
        var end = selections[selections.length-1];
2814
        var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
2815
        var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
2816
        return [selectionStart, selectionEnd];
2817
      };
2818
      var getLastSelectedAreaRange = function() {
2819
        var selectionStart = cm.getCursor();
2820
        var selectionEnd = cm.getCursor();
2821
        var block = lastSelection.visualBlock;
2822
        if (block) {
2823
          var width = block.width;
2824
          var height = block.height;
2825
          selectionEnd = Pos(selectionStart.line + height, selectionStart.ch + width);
2826
          var selections = [];
2827
          // selectBlock creates a 'proper' rectangular block.
2828
          // We do not want that in all cases, so we manually set selections.
2829
          for (var i = selectionStart.line; i < selectionEnd.line; i++) {
2830
            var anchor = Pos(i, selectionStart.ch);
2831
            var head = Pos(i, selectionEnd.ch);
2832
            var range = {anchor: anchor, head: head};
2833
            selections.push(range);
2834
          }
2835
          cm.setSelections(selections);
2836
        } else {
2837
          var start = lastSelection.anchorMark.find();
2838
          var end = lastSelection.headMark.find();
2839
          var line = end.line - start.line;
2840
          var ch = end.ch - start.ch;
2841
          selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch};
2842
          if (lastSelection.visualLine) {
2843
            selectionStart = Pos(selectionStart.line, 0);
2844
            selectionEnd = Pos(selectionEnd.line, lineLength(cm, selectionEnd.line));
2845
          }
2846
          cm.setSelection(selectionStart, selectionEnd);
2847
        }
2848
        return [selectionStart, selectionEnd];
2849
      };
2850
      if (!vim.visualMode) {
2851
      // In case of replaying the action.
2852
        return getLastSelectedAreaRange();
2853
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
2854
        return getCurrentSelectedAreaRange();
2855
      }
2856
    }
2857
    // Updates the previous selection with the current selection's values. This
2858
    // should only be called in visual mode.
2859
    function updateLastSelection(cm, vim) {
2860
      var anchor = vim.sel.anchor;
2861
      var head = vim.sel.head;
2862
      // To accommodate the effect of lastPastedText in the last selection
2863
      if (vim.lastPastedText) {
2864
        head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length);
2865
        vim.lastPastedText = null;
2866
      }
2867
      vim.lastSelection = {'anchorMark': cm.setBookmark(anchor),
2868
                           'headMark': cm.setBookmark(head),
2869
                           'anchor': copyCursor(anchor),
2870
                           'head': copyCursor(head),
2871
                           'visualMode': vim.visualMode,
2872
                           'visualLine': vim.visualLine,
2873
                           'visualBlock': vim.visualBlock};
2874
    }
2875
    function expandSelection(cm, start, end) {
2876
      var sel = cm.state.vim.sel;
2877
      var head = sel.head;
2878
      var anchor = sel.anchor;
2879
      var tmp;
2880
      if (cursorIsBefore(end, start)) {
2881
        tmp = end;
2882
        end = start;
2883
        start = tmp;
2884
      }
2885
      if (cursorIsBefore(head, anchor)) {
2886
        head = cursorMin(start, head);
2887
        anchor = cursorMax(anchor, end);
2888
      } else {
2889
        anchor = cursorMin(start, anchor);
2890
        head = cursorMax(head, end);
2891
        head = offsetCursor(head, 0, -1);
2892
        if (head.ch == -1 && head.line != cm.firstLine()) {
2893
          head = Pos(head.line - 1, lineLength(cm, head.line - 1));
2894
        }
2895
      }
2896
      return [anchor, head];
2897
    }
2898
    /**
2899
     * Updates the CodeMirror selection to match the provided vim selection.
2900
     * If no arguments are given, it uses the current vim selection state.
2901
     */
2902
    function updateCmSelection(cm, sel, mode) {
2903
      var vim = cm.state.vim;
2904
      sel = sel || vim.sel;
2905
      var mode = mode ||
2906
        vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char';
2907
      var cmSel = makeCmSelection(cm, sel, mode);
2908
      cm.setSelections(cmSel.ranges, cmSel.primary);
2909
      updateFakeCursor(cm);
2910
    }
2911
    function makeCmSelection(cm, sel, mode, exclusive) {
2912
      var head = copyCursor(sel.head);
2913
      var anchor = copyCursor(sel.anchor);
2914
      if (mode == 'char') {
2915
        var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
2916
        var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
2917
        head = offsetCursor(sel.head, 0, headOffset);
2918
        anchor = offsetCursor(sel.anchor, 0, anchorOffset);
2919
        return {
2920
          ranges: [{anchor: anchor, head: head}],
2921
          primary: 0
2922
        };
2923
      } else if (mode == 'line') {
2924
        if (!cursorIsBefore(sel.head, sel.anchor)) {
2925
          anchor.ch = 0;
2926
2927
          var lastLine = cm.lastLine();
2928
          if (head.line > lastLine) {
2929
            head.line = lastLine;
2930
          }
2931
          head.ch = lineLength(cm, head.line);
2932
        } else {
2933
          head.ch = 0;
2934
          anchor.ch = lineLength(cm, anchor.line);
2935
        }
2936
        return {
2937
          ranges: [{anchor: anchor, head: head}],
2938
          primary: 0
2939
        };
2940
      } else if (mode == 'block') {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if mode == "block" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
2941
        var top = Math.min(anchor.line, head.line),
2942
            left = Math.min(anchor.ch, head.ch),
2943
            bottom = Math.max(anchor.line, head.line),
2944
            right = Math.max(anchor.ch, head.ch) + 1;
2945
        var height = bottom - top + 1;
2946
        var primary = head.line == top ? 0 : height - 1;
2947
        var ranges = [];
2948
        for (var i = 0; i < height; i++) {
2949
          ranges.push({
2950
            anchor: Pos(top + i, left),
2951
            head: Pos(top + i, right)
2952
          });
2953
        }
2954
        return {
2955
          ranges: ranges,
2956
          primary: primary
2957
        };
2958
      }
2959
    }
2960
    function getHead(cm) {
2961
      var cur = cm.getCursor('head');
2962
      if (cm.getSelection().length == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing cm.getSelection().length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
2963
        // Small corner case when only 1 character is selected. The "real"
2964
        // head is the left of head and anchor.
2965
        cur = cursorMin(cur, cm.getCursor('anchor'));
2966
      }
2967
      return cur;
2968
    }
2969
2970
    /**
2971
     * If moveHead is set to false, the CodeMirror selection will not be
2972
     * touched. The caller assumes the responsibility of putting the cursor
2973
    * in the right place.
2974
     */
2975
    function exitVisualMode(cm, moveHead) {
2976
      var vim = cm.state.vim;
2977
      if (moveHead !== false) {
2978
        cm.setCursor(clipCursorToContent(cm, vim.sel.head));
2979
      }
2980
      updateLastSelection(cm, vim);
2981
      vim.visualMode = false;
2982
      vim.visualLine = false;
2983
      vim.visualBlock = false;
2984
      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
2985
      if (vim.fakeCursor) {
2986
        vim.fakeCursor.clear();
2987
      }
2988
    }
2989
2990
    // Remove any trailing newlines from the selection. For
2991
    // example, with the caret at the start of the last word on the line,
2992
    // 'dw' should word, but not the newline, while 'w' should advance the
2993
    // caret to the first character of the next line.
2994
    function clipToLine(cm, curStart, curEnd) {
2995
      var selection = cm.getRange(curStart, curEnd);
2996
      // Only clip if the selection ends with trailing newline + whitespace
2997
      if (/\n\s*$/.test(selection)) {
2998
        var lines = selection.split('\n');
2999
        // We know this is all whitepsace.
3000
        lines.pop();
3001
3002
        // Cases:
3003
        // 1. Last word is an empty line - do not clip the trailing '\n'
3004
        // 2. Last word is not an empty line - clip the trailing '\n'
3005
        var line;
3006
        // Find the line containing the last word, and clip all whitespace up
3007
        // to it.
3008
        for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable line already seems to be declared on line 3005. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
3009
          curEnd.line--;
3010
          curEnd.ch = 0;
3011
        }
3012
        // If the last word is not an empty line, clip an additional newline
3013
        if (line) {
3014
          curEnd.line--;
3015
          curEnd.ch = lineLength(cm, curEnd.line);
3016
        } else {
3017
          curEnd.ch = 0;
3018
        }
3019
      }
3020
    }
3021
3022
    // Expand the selection to line ends.
3023
    function expandSelectionToLine(_cm, curStart, curEnd) {
3024
      curStart.ch = 0;
3025
      curEnd.ch = 0;
3026
      curEnd.line++;
3027
    }
3028
3029
    function findFirstNonWhiteSpaceCharacter(text) {
3030
      if (!text) {
3031
        return 0;
3032
      }
3033
      var firstNonWS = text.search(/\S/);
3034
      return firstNonWS == -1 ? text.length : firstNonWS;
3035
    }
3036
3037
    function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
3038
      var cur = getHead(cm);
3039
      var line = cm.getLine(cur.line);
3040
      var idx = cur.ch;
3041
3042
      // Seek to first word or non-whitespace character, depending on if
3043
      // noSymbol is true.
3044
      var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0];
3045
      while (!test(line.charAt(idx))) {
3046
        idx++;
3047
        if (idx >= line.length) { return null; }
3048
      }
3049
3050
      if (bigWord) {
3051
        test = bigWordCharTest[0];
3052
      } else {
3053
        test = wordCharTest[0];
3054
        if (!test(line.charAt(idx))) {
3055
          test = wordCharTest[1];
3056
        }
3057
      }
3058
3059
      var end = idx, start = idx;
3060
      while (test(line.charAt(end)) && end < line.length) { end++; }
3061
      while (test(line.charAt(start)) && start >= 0) { start--; }
3062
      start++;
3063
3064
      if (inclusive) {
3065
        // If present, include all whitespace after word.
3066
        // Otherwise, include all whitespace before word, except indentation.
3067
        var wordEnd = end;
3068
        while (/\s/.test(line.charAt(end)) && end < line.length) { end++; }
3069
        if (wordEnd == end) {
3070
          var wordStart = start;
3071
          while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; }
3072
          if (!start) { start = wordStart; }
3073
        }
3074
      }
3075
      return { start: Pos(cur.line, start), end: Pos(cur.line, end) };
3076
    }
3077
3078
    function recordJumpPosition(cm, oldCur, newCur) {
3079
      if (!cursorEqual(oldCur, newCur)) {
3080
        vimGlobalState.jumpList.add(cm, oldCur, newCur);
3081
      }
3082
    }
3083
3084
    function recordLastCharacterSearch(increment, args) {
3085
        vimGlobalState.lastChararacterSearch.increment = increment;
3086
        vimGlobalState.lastChararacterSearch.forward = args.forward;
3087
        vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter;
3088
    }
3089
3090
    var symbolToMode = {
3091
        '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',
3092
        '[': 'section', ']': 'section',
3093
        '*': 'comment', '/': 'comment',
3094
        'm': 'method', 'M': 'method',
3095
        '#': 'preprocess'
3096
    };
3097
    var findSymbolModes = {
3098
      bracket: {
3099
        isComplete: function(state) {
3100
          if (state.nextCh === state.symb) {
3101
            state.depth++;
3102
            if (state.depth >= 1)return true;
3103
          } else if (state.nextCh === state.reverseSymb) {
3104
            state.depth--;
3105
          }
3106
          return false;
3107
        }
3108
      },
3109
      section: {
3110
        init: function(state) {
3111
          state.curMoveThrough = true;
3112
          state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';
3113
        },
3114
        isComplete: function(state) {
3115
          return state.index === 0 && state.nextCh === state.symb;
3116
        }
3117
      },
3118
      comment: {
3119
        isComplete: function(state) {
3120
          var found = state.lastCh === '*' && state.nextCh === '/';
3121
          state.lastCh = state.nextCh;
3122
          return found;
3123
        }
3124
      },
3125
      // TODO: The original Vim implementation only operates on level 1 and 2.
3126
      // The current implementation doesn't check for code block level and
3127
      // therefore it operates on any levels.
3128
      method: {
3129
        init: function(state) {
3130
          state.symb = (state.symb === 'm' ? '{' : '}');
3131
          state.reverseSymb = state.symb === '{' ? '}' : '{';
3132
        },
3133
        isComplete: function(state) {
3134
          if (state.nextCh === state.symb)return true;
3135
          return false;
3136
        }
3137
      },
3138
      preprocess: {
3139
        init: function(state) {
3140
          state.index = 0;
3141
        },
3142
        isComplete: function(state) {
3143
          if (state.nextCh === '#') {
3144
            var token = state.lineText.match(/#(\w+)/)[1];
3145
            if (token === 'endif') {
3146
              if (state.forward && state.depth === 0) {
3147
                return true;
3148
              }
3149
              state.depth++;
3150
            } else if (token === 'if') {
3151
              if (!state.forward && state.depth === 0) {
3152
                return true;
3153
              }
3154
              state.depth--;
3155
            }
3156
            if (token === 'else' && state.depth === 0)return true;
3157
          }
3158
          return false;
3159
        }
3160
      }
3161
    };
3162
    function findSymbol(cm, repeat, forward, symb) {
3163
      var cur = copyCursor(cm.getCursor());
3164
      var increment = forward ? 1 : -1;
3165
      var endLine = forward ? cm.lineCount() : -1;
3166
      var curCh = cur.ch;
3167
      var line = cur.line;
3168
      var lineText = cm.getLine(line);
3169
      var state = {
3170
        lineText: lineText,
3171
        nextCh: lineText.charAt(curCh),
3172
        lastCh: null,
3173
        index: curCh,
3174
        symb: symb,
3175
        reverseSymb: (forward ?  { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
3176
        forward: forward,
3177
        depth: 0,
3178
        curMoveThrough: false
3179
      };
3180
      var mode = symbolToMode[symb];
3181
      if (!mode)return cur;
3182
      var init = findSymbolModes[mode].init;
3183
      var isComplete = findSymbolModes[mode].isComplete;
3184
      if (init) { init(state); }
3185
      while (line !== endLine && repeat) {
3186
        state.index += increment;
3187
        state.nextCh = state.lineText.charAt(state.index);
3188
        if (!state.nextCh) {
3189
          line += increment;
3190
          state.lineText = cm.getLine(line) || '';
3191
          if (increment > 0) {
3192
            state.index = 0;
3193
          } else {
3194
            var lineLen = state.lineText.length;
3195
            state.index = (lineLen > 0) ? (lineLen-1) : 0;
3196
          }
3197
          state.nextCh = state.lineText.charAt(state.index);
3198
        }
3199
        if (isComplete(state)) {
3200
          cur.line = line;
3201
          cur.ch = state.index;
3202
          repeat--;
3203
        }
3204
      }
3205
      if (state.nextCh || state.curMoveThrough) {
3206
        return Pos(line, state.index);
3207
      }
3208
      return cur;
3209
    }
3210
3211
    /*
3212
     * Returns the boundaries of the next word. If the cursor in the middle of
3213
     * the word, then returns the boundaries of the current word, starting at
3214
     * the cursor. If the cursor is at the start/end of a word, and we are going
3215
     * forward/backward, respectively, find the boundaries of the next word.
3216
     *
3217
     * @param {CodeMirror} cm CodeMirror object.
3218
     * @param {Cursor} cur The cursor position.
3219
     * @param {boolean} forward True to search forward. False to search
3220
     *     backward.
3221
     * @param {boolean} bigWord True if punctuation count as part of the word.
3222
     *     False if only [a-zA-Z0-9] characters count as part of the word.
3223
     * @param {boolean} emptyLineIsWord True if empty lines should be treated
3224
     *     as words.
3225
     * @return {Object{from:number, to:number, line: number}} The boundaries of
3226
     *     the word, or null if there are no more words.
3227
     */
3228
    function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {
3229
      var lineNum = cur.line;
3230
      var pos = cur.ch;
3231
      var line = cm.getLine(lineNum);
3232
      var dir = forward ? 1 : -1;
3233
      var charTests = bigWord ? bigWordCharTest: wordCharTest;
3234
3235
      if (emptyLineIsWord && line == '') {
3236
        lineNum += dir;
3237
        line = cm.getLine(lineNum);
3238
        if (!isLine(cm, lineNum)) {
3239
          return null;
3240
        }
3241
        pos = (forward) ? 0 : line.length;
3242
      }
3243
3244
      while (true) {
3245
        if (emptyLineIsWord && line == '') {
3246
          return { from: 0, to: 0, line: lineNum };
3247
        }
3248
        var stop = (dir > 0) ? line.length : -1;
3249
        var wordStart = stop, wordEnd = stop;
0 ignored issues
show
Unused Code introduced by
The assignment to variable wordStart seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The assignment to variable wordEnd seems to be never used. Consider removing it.
Loading history...
3250
        // Find bounds of next word.
3251
        while (pos != stop) {
3252
          var foundWord = false;
3253
          for (var i = 0; i < charTests.length && !foundWord; ++i) {
3254
            if (charTests[i](line.charAt(pos))) {
3255
              wordStart = pos;
3256
              // Advance to end of word.
3257
              while (pos != stop && charTests[i](line.charAt(pos))) {
3258
                pos += dir;
3259
              }
3260
              wordEnd = pos;
3261
              foundWord = wordStart != wordEnd;
3262
              if (wordStart == cur.ch && lineNum == cur.line &&
3263
                  wordEnd == wordStart + dir) {
3264
                // We started at the end of a word. Find the next one.
3265
                continue;
0 ignored issues
show
Unused Code introduced by
This continue has no effect on the loop flow and can be removed.
Loading history...
3266
              } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
3267
                return {
3268
                  from: Math.min(wordStart, wordEnd + 1),
3269
                  to: Math.max(wordStart, wordEnd),
3270
                  line: lineNum };
3271
              }
3272
            }
3273
          }
3274
          if (!foundWord) {
3275
            pos += dir;
3276
          }
3277
        }
3278
        // Advance to next/prev line.
3279
        lineNum += dir;
3280
        if (!isLine(cm, lineNum)) {
3281
          return null;
3282
        }
3283
        line = cm.getLine(lineNum);
3284
        pos = (dir > 0) ? 0 : line.length;
3285
      }
3286
      // Should never get here.
3287
      throw new Error('The impossible happened.');
3288
    }
3289
3290
    /**
3291
     * @param {CodeMirror} cm CodeMirror object.
3292
     * @param {Pos} cur The position to start from.
3293
     * @param {int} repeat Number of words to move past.
3294
     * @param {boolean} forward True to search forward. False to search
3295
     *     backward.
3296
     * @param {boolean} wordEnd True to move to end of word. False to move to
3297
     *     beginning of word.
3298
     * @param {boolean} bigWord True if punctuation count as part of the word.
3299
     *     False if only alphabet characters count as part of the word.
3300
     * @return {Cursor} The position the cursor should move to.
3301
     */
3302
    function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) {
3303
      var curStart = copyCursor(cur);
3304
      var words = [];
3305
      if (forward && !wordEnd || !forward && wordEnd) {
3306
        repeat++;
3307
      }
3308
      // For 'e', empty lines are not considered words, go figure.
3309
      var emptyLineIsWord = !(forward && wordEnd);
3310
      for (var i = 0; i < repeat; i++) {
3311
        var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);
3312
        if (!word) {
3313
          var eodCh = lineLength(cm, cm.lastLine());
3314
          words.push(forward
3315
              ? {line: cm.lastLine(), from: eodCh, to: eodCh}
3316
              : {line: 0, from: 0, to: 0});
3317
          break;
3318
        }
3319
        words.push(word);
3320
        cur = Pos(word.line, forward ? (word.to - 1) : word.from);
3321
      }
3322
      var shortCircuit = words.length != repeat;
3323
      var firstWord = words[0];
3324
      var lastWord = words.pop();
3325
      if (forward && !wordEnd) {
3326
        // w
3327
        if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {
3328
          // We did not start in the middle of a word. Discard the extra word at the end.
3329
          lastWord = words.pop();
3330
        }
3331
        return Pos(lastWord.line, lastWord.from);
3332
      } else if (forward && wordEnd) {
3333
        return Pos(lastWord.line, lastWord.to - 1);
3334
      } else if (!forward && wordEnd) {
3335
        // ge
3336
        if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {
3337
          // We did not start in the middle of a word. Discard the extra word at the end.
3338
          lastWord = words.pop();
3339
        }
3340
        return Pos(lastWord.line, lastWord.to);
3341
      } else {
3342
        // b
3343
        return Pos(lastWord.line, lastWord.from);
3344
      }
3345
    }
3346
3347
    function moveToCharacter(cm, repeat, forward, character) {
3348
      var cur = cm.getCursor();
3349
      var start = cur.ch;
3350
      var idx;
3351
      for (var i = 0; i < repeat; i ++) {
3352
        var line = cm.getLine(cur.line);
3353
        idx = charIdxInLine(start, line, character, forward, true);
3354
        if (idx == -1) {
3355
          return null;
3356
        }
3357
        start = idx;
3358
      }
3359
      return Pos(cm.getCursor().line, idx);
0 ignored issues
show
Bug introduced by
The variable idx seems to not be initialized for all possible execution paths. Are you sure Pos handles undefined variables?
Loading history...
3360
    }
3361
3362
    function moveToColumn(cm, repeat) {
3363
      // repeat is always >= 1, so repeat - 1 always corresponds
3364
      // to the column we want to go to.
3365
      var line = cm.getCursor().line;
3366
      return clipCursorToContent(cm, Pos(line, repeat - 1));
3367
    }
3368
3369
    function updateMark(cm, vim, markName, pos) {
3370
      if (!inArray(markName, validMarks)) {
3371
        return;
3372
      }
3373
      if (vim.marks[markName]) {
3374
        vim.marks[markName].clear();
3375
      }
3376
      vim.marks[markName] = cm.setBookmark(pos);
3377
    }
3378
3379
    function charIdxInLine(start, line, character, forward, includeChar) {
3380
      // Search for char in line.
3381
      // motion_options: {forward, includeChar}
3382
      // If includeChar = true, include it too.
3383
      // If forward = true, search forward, else search backwards.
3384
      // If char is not found on this line, do nothing
3385
      var idx;
3386
      if (forward) {
3387
        idx = line.indexOf(character, start + 1);
3388
        if (idx != -1 && !includeChar) {
3389
          idx -= 1;
3390
        }
3391
      } else {
3392
        idx = line.lastIndexOf(character, start - 1);
3393
        if (idx != -1 && !includeChar) {
3394
          idx += 1;
3395
        }
3396
      }
3397
      return idx;
3398
    }
3399
3400
    function findParagraph(cm, head, repeat, dir, inclusive) {
3401
      var line = head.line;
3402
      var min = cm.firstLine();
3403
      var max = cm.lastLine();
3404
      var start, end, i = line;
3405
      function isEmpty(i) { return !cm.getLine(i); }
3406
      function isBoundary(i, dir, any) {
3407
        if (any) { return isEmpty(i) != isEmpty(i + dir); }
3408
        return !isEmpty(i) && isEmpty(i + dir);
3409
      }
3410
      if (dir) {
3411
        while (min <= i && i <= max && repeat > 0) {
3412
          if (isBoundary(i, dir)) { repeat--; }
3413
          i += dir;
3414
        }
3415
        return new Pos(i, 0);
3416
      }
3417
3418
      var vim = cm.state.vim;
3419
      if (vim.visualLine && isBoundary(line, 1, true)) {
3420
        var anchor = vim.sel.anchor;
3421
        if (isBoundary(anchor.line, -1, true)) {
3422
          if (!inclusive || anchor.line != line) {
3423
            line += 1;
3424
          }
3425
        }
3426
      }
3427
      var startState = isEmpty(line);
3428
      for (i = line; i <= max && repeat; i++) {
3429
        if (isBoundary(i, 1, true)) {
3430
          if (!inclusive || isEmpty(i) != startState) {
3431
            repeat--;
3432
          }
3433
        }
3434
      }
3435
      end = new Pos(i, 0);
3436
      // select boundary before paragraph for the last one
3437
      if (i > max && !startState) { startState = true; }
3438
      else { inclusive = false; }
3439
      for (i = line; i > min; i--) {
3440
        if (!inclusive || isEmpty(i) == startState || i == line) {
3441
          if (isBoundary(i, -1, true)) { break; }
3442
        }
3443
      }
3444
      start = new Pos(i, 0);
3445
      return { start: start, end: end };
3446
    }
3447
3448
    // TODO: perhaps this finagling of start and end positions belonds
3449
    // in codmirror/replaceRange?
3450
    function selectCompanionObject(cm, head, symb, inclusive) {
3451
      var cur = head, start, end;
3452
3453
      var bracketRegexp = ({
3454
        '(': /[()]/, ')': /[()]/,
3455
        '[': /[[\]]/, ']': /[[\]]/,
3456
        '{': /[{}]/, '}': /[{}]/})[symb];
3457
      var openSym = ({
3458
        '(': '(', ')': '(',
3459
        '[': '[', ']': '[',
3460
        '{': '{', '}': '{'})[symb];
3461
      var curChar = cm.getLine(cur.line).charAt(cur.ch);
3462
      // Due to the behavior of scanForBracket, we need to add an offset if the
3463
      // cursor is on a matching open bracket.
3464
      var offset = curChar === openSym ? 1 : 0;
3465
3466
      start = cm.scanForBracket(Pos(cur.line, cur.ch + offset), -1, null, {'bracketRegex': bracketRegexp});
3467
      end = cm.scanForBracket(Pos(cur.line, cur.ch + offset), 1, null, {'bracketRegex': bracketRegexp});
3468
3469
      if (!start || !end) {
3470
        return { start: cur, end: cur };
3471
      }
3472
3473
      start = start.pos;
3474
      end = end.pos;
3475
3476
      if ((start.line == end.line && start.ch > end.ch)
3477
          || (start.line > end.line)) {
3478
        var tmp = start;
3479
        start = end;
3480
        end = tmp;
3481
      }
3482
3483
      if (inclusive) {
3484
        end.ch += 1;
3485
      } else {
3486
        start.ch += 1;
3487
      }
3488
3489
      return { start: start, end: end };
3490
    }
3491
3492
    // Takes in a symbol and a cursor and tries to simulate text objects that
3493
    // have identical opening and closing symbols
3494
    // TODO support across multiple lines
3495
    function findBeginningAndEnd(cm, head, symb, inclusive) {
3496
      var cur = copyCursor(head);
3497
      var line = cm.getLine(cur.line);
3498
      var chars = line.split('');
3499
      var start, end, i, len;
3500
      var firstIndex = chars.indexOf(symb);
3501
3502
      // the decision tree is to always look backwards for the beginning first,
3503
      // but if the cursor is in front of the first instance of the symb,
3504
      // then move the cursor forward
3505
      if (cur.ch < firstIndex) {
3506
        cur.ch = firstIndex;
3507
        // Why is this line even here???
3508
        // cm.setCursor(cur.line, firstIndex+1);
3509
      }
3510
      // otherwise if the cursor is currently on the closing symbol
3511
      else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
3512
        end = cur.ch; // assign end to the current cursor
3513
        --cur.ch; // make sure to look backwards
3514
      }
3515
3516
      // if we're currently on the symbol, we've got a start
3517
      if (chars[cur.ch] == symb && !end) {
3518
        start = cur.ch + 1; // assign start to ahead of the cursor
3519
      } else {
3520
        // go backwards to find the start
3521
        for (i = cur.ch; i > -1 && !start; i--) {
3522
          if (chars[i] == symb) {
3523
            start = i + 1;
3524
          }
3525
        }
3526
      }
3527
3528
      // look forwards for the end symbol
3529
      if (start && !end) {
3530
        for (i = start, len = chars.length; i < len && !end; i++) {
3531
          if (chars[i] == symb) {
3532
            end = i;
3533
          }
3534
        }
3535
      }
3536
3537
      // nothing found
3538
      if (!start || !end) {
3539
        return { start: cur, end: cur };
3540
      }
3541
3542
      // include the symbols
3543
      if (inclusive) {
3544
        --start; ++end;
3545
      }
3546
3547
      return {
3548
        start: Pos(cur.line, start),
3549
        end: Pos(cur.line, end)
3550
      };
3551
    }
3552
3553
    // Search functions
3554
    defineOption('pcre', true, 'boolean');
3555
    function SearchState() {}
3556
    SearchState.prototype = {
3557
      getQuery: function() {
3558
        return vimGlobalState.query;
3559
      },
3560
      setQuery: function(query) {
3561
        vimGlobalState.query = query;
3562
      },
3563
      getOverlay: function() {
3564
        return this.searchOverlay;
3565
      },
3566
      setOverlay: function(overlay) {
3567
        this.searchOverlay = overlay;
3568
      },
3569
      isReversed: function() {
3570
        return vimGlobalState.isReversed;
3571
      },
3572
      setReversed: function(reversed) {
3573
        vimGlobalState.isReversed = reversed;
3574
      },
3575
      getScrollbarAnnotate: function() {
3576
        return this.annotate;
3577
      },
3578
      setScrollbarAnnotate: function(annotate) {
3579
        this.annotate = annotate;
3580
      }
3581
    };
3582
    function getSearchState(cm) {
3583
      var vim = cm.state.vim;
3584
      return vim.searchState_ || (vim.searchState_ = new SearchState());
3585
    }
3586
    function dialog(cm, template, shortText, onClose, options) {
3587
      if (cm.openDialog) {
3588
        cm.openDialog(template, onClose, { bottom: true, value: options.value,
3589
            onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp,
3590
            selectValueOnOpen: false});
3591
      }
3592
      else {
3593
        onClose(prompt(shortText, ''));
0 ignored issues
show
Debugging Code Best Practice introduced by
The prompt UI element is often considered obtrusive and is generally only used as a temporary measure. Consider replacing it with another UI element.
Loading history...
3594
      }
3595
    }
3596
    function splitBySlash(argString) {
3597
      var slashes = findUnescapedSlashes(argString) || [];
3598
      if (!slashes.length) return [];
3599
      var tokens = [];
3600
      // in case of strings like foo/bar
3601
      if (slashes[0] !== 0) return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
3602
      for (var i = 0; i < slashes.length; i++) {
3603
        if (typeof slashes[i] == 'number')
3604
          tokens.push(argString.substring(slashes[i] + 1, slashes[i+1]));
3605
      }
3606
      return tokens;
3607
    }
3608
3609
    function findUnescapedSlashes(str) {
3610
      var escapeNextChar = false;
3611
      var slashes = [];
3612
      for (var i = 0; i < str.length; i++) {
3613
        var c = str.charAt(i);
3614
        if (!escapeNextChar && c == '/') {
3615
          slashes.push(i);
3616
        }
3617
        escapeNextChar = !escapeNextChar && (c == '\\');
3618
      }
3619
      return slashes;
3620
    }
3621
3622
    // Translates a search string from ex (vim) syntax into javascript form.
3623
    function translateRegex(str) {
3624
      // When these match, add a '\' if unescaped or remove one if escaped.
3625
      var specials = '|(){';
3626
      // Remove, but never add, a '\' for these.
3627
      var unescape = '}';
3628
      var escapeNextChar = false;
3629
      var out = [];
3630
      for (var i = -1; i < str.length; i++) {
3631
        var c = str.charAt(i) || '';
3632
        var n = str.charAt(i+1) || '';
3633
        var specialComesNext = (n && specials.indexOf(n) != -1);
3634
        if (escapeNextChar) {
3635
          if (c !== '\\' || !specialComesNext) {
3636
            out.push(c);
3637
          }
3638
          escapeNextChar = false;
3639
        } else {
3640
          if (c === '\\') {
3641
            escapeNextChar = true;
3642
            // Treat the unescape list as special for removing, but not adding '\'.
3643
            if (n && unescape.indexOf(n) != -1) {
3644
              specialComesNext = true;
3645
            }
3646
            // Not passing this test means removing a '\'.
3647
            if (!specialComesNext || n === '\\') {
3648
              out.push(c);
3649
            }
3650
          } else {
3651
            out.push(c);
3652
            if (specialComesNext && n !== '\\') {
3653
              out.push('\\');
3654
            }
3655
          }
3656
        }
3657
      }
3658
      return out.join('');
3659
    }
3660
3661
    // Translates the replace part of a search and replace from ex (vim) syntax into
3662
    // javascript form.  Similar to translateRegex, but additionally fixes back references
3663
    // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'.
3664
    var charUnescapes = {'\\n': '\n', '\\r': '\r', '\\t': '\t'};
3665
    function translateRegexReplace(str) {
3666
      var escapeNextChar = false;
3667
      var out = [];
3668
      for (var i = -1; i < str.length; i++) {
3669
        var c = str.charAt(i) || '';
3670
        var n = str.charAt(i+1) || '';
3671
        if (charUnescapes[c + n]) {
3672
          out.push(charUnescapes[c+n]);
3673
          i++;
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable i here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
3674
        } else if (escapeNextChar) {
3675
          // At any point in the loop, escapeNextChar is true if the previous
3676
          // character was a '\' and was not escaped.
3677
          out.push(c);
3678
          escapeNextChar = false;
3679
        } else {
3680
          if (c === '\\') {
3681
            escapeNextChar = true;
3682
            if ((isNumber(n) || n === '$')) {
3683
              out.push('$');
3684
            } else if (n !== '/' && n !== '\\') {
3685
              out.push('\\');
3686
            }
3687
          } else {
3688
            if (c === '$') {
3689
              out.push('$');
3690
            }
3691
            out.push(c);
3692
            if (n === '/') {
3693
              out.push('\\');
3694
            }
3695
          }
3696
        }
3697
      }
3698
      return out.join('');
3699
    }
3700
3701
    // Unescape \ and / in the replace part, for PCRE mode.
3702
    var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t'};
3703
    function unescapeRegexReplace(str) {
3704
      var stream = new CodeMirror.StringStream(str);
3705
      var output = [];
3706
      while (!stream.eol()) {
3707
        // Search for \.
3708
        while (stream.peek() && stream.peek() != '\\') {
3709
          output.push(stream.next());
3710
        }
3711
        var matched = false;
3712
        for (var matcher in unescapes) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
3713
          if (stream.match(matcher, true)) {
3714
            matched = true;
3715
            output.push(unescapes[matcher]);
3716
            break;
3717
          }
3718
        }
3719
        if (!matched) {
3720
          // Don't change anything
3721
          output.push(stream.next());
3722
        }
3723
      }
3724
      return output.join('');
3725
    }
3726
3727
    /**
3728
     * Extract the regular expression from the query and return a Regexp object.
3729
     * Returns null if the query is blank.
3730
     * If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
3731
     * If smartCase is passed in, and the query contains upper case letters,
3732
     *   then ignoreCase is overridden, and the 'i' flag will not be set.
3733
     * If the query contains the /i in the flag part of the regular expression,
3734
     *   then both ignoreCase and smartCase are ignored, and 'i' will be passed
3735
     *   through to the Regex object.
3736
     */
3737
    function parseQuery(query, ignoreCase, smartCase) {
3738
      // First update the last search register
3739
      var lastSearchRegister = vimGlobalState.registerController.getRegister('/');
3740
      lastSearchRegister.setText(query);
3741
      // Check if the query is already a regex.
3742
      if (query instanceof RegExp) { return query; }
3743
      // First try to extract regex + flags from the input. If no flags found,
3744
      // extract just the regex. IE does not accept flags directly defined in
3745
      // the regex string in the form /regex/flags
3746
      var slashes = findUnescapedSlashes(query);
3747
      var regexPart;
3748
      var forceIgnoreCase;
3749
      if (!slashes.length) {
3750
        // Query looks like 'regexp'
3751
        regexPart = query;
3752
      } else {
3753
        // Query looks like 'regexp/...'
3754
        regexPart = query.substring(0, slashes[0]);
3755
        var flagsPart = query.substring(slashes[0]);
3756
        forceIgnoreCase = (flagsPart.indexOf('i') != -1);
3757
      }
3758
      if (!regexPart) {
3759
        return null;
3760
      }
3761
      if (!getOption('pcre')) {
3762
        regexPart = translateRegex(regexPart);
3763
      }
3764
      if (smartCase) {
3765
        ignoreCase = (/^[^A-Z]*$/).test(regexPart);
3766
      }
3767
      var regexp = new RegExp(regexPart,
3768
          (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
3769
      return regexp;
3770
    }
3771
    function showConfirm(cm, text) {
3772
      if (cm.openNotification) {
3773
        cm.openNotification('<span style="color: red">' + text + '</span>',
3774
                            {bottom: true, duration: 5000});
3775
      } else {
3776
        alert(text);
0 ignored issues
show
Debugging Code Best Practice introduced by
The alert UI element is often considered obtrusive and is generally only used as a temporary measure. Consider replacing it with another UI element.
Loading history...
3777
      }
3778
    }
3779
    function makePrompt(prefix, desc) {
3780
      var raw = '';
3781
      if (prefix) {
3782
        raw += '<span style="font-family: monospace">' + prefix + '</span>';
3783
      }
3784
      raw += '<input type="text"/> ' +
3785
          '<span style="color: #888">';
3786
      if (desc) {
3787
        raw += '<span style="color: #888">';
3788
        raw += desc;
3789
        raw += '</span>';
3790
      }
3791
      return raw;
3792
    }
3793
    var searchPromptDesc = '(Javascript regexp)';
3794
    function showPrompt(cm, options) {
3795
      var shortText = (options.prefix || '') + ' ' + (options.desc || '');
3796
      var prompt = makePrompt(options.prefix, options.desc);
3797
      dialog(cm, prompt, shortText, options.onClose, options);
3798
    }
3799
    function regexEqual(r1, r2) {
3800
      if (r1 instanceof RegExp && r2 instanceof RegExp) {
3801
          var props = ['global', 'multiline', 'ignoreCase', 'source'];
3802
          for (var i = 0; i < props.length; i++) {
3803
              var prop = props[i];
3804
              if (r1[prop] !== r2[prop]) {
3805
                  return false;
3806
              }
3807
          }
3808
          return true;
3809
      }
3810
      return false;
3811
    }
3812
    // Returns true if the query is valid.
3813
    function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
3814
      if (!rawQuery) {
3815
        return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
3816
      }
3817
      var state = getSearchState(cm);
3818
      var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);
3819
      if (!query) {
3820
        return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
3821
      }
3822
      highlightSearchMatches(cm, query);
3823
      if (regexEqual(query, state.getQuery())) {
3824
        return query;
3825
      }
3826
      state.setQuery(query);
3827
      return query;
3828
    }
3829
    function searchOverlay(query) {
3830
      if (query.source.charAt(0) == '^') {
3831
        var matchSol = true;
3832
      }
3833
      return {
3834
        token: function(stream) {
3835
          if (matchSol && !stream.sol()) {
3836
            stream.skipToEnd();
3837
            return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
3838
          }
3839
          var match = stream.match(query, false);
3840
          if (match) {
3841
            if (match[0].length == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing match.0.length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
3842
              // Matched empty string, skip to next.
3843
              stream.next();
3844
              return 'searching';
3845
            }
3846
            if (!stream.sol()) {
3847
              // Backtrack 1 to match \b
3848
              stream.backUp(1);
3849
              if (!query.exec(stream.next() + match[0])) {
3850
                stream.next();
3851
                return null;
3852
              }
3853
            }
3854
            stream.match(query);
3855
            return 'searching';
3856
          }
3857
          while (!stream.eol()) {
3858
            stream.next();
3859
            if (stream.match(query, false)) break;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3860
          }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3861
        },
3862
        query: query
3863
      };
3864
    }
3865
    function highlightSearchMatches(cm, query) {
3866
      var searchState = getSearchState(cm);
3867
      var overlay = searchState.getOverlay();
3868
      if (!overlay || query != overlay.query) {
3869
        if (overlay) {
3870
          cm.removeOverlay(overlay);
3871
        }
3872
        overlay = searchOverlay(query);
3873
        cm.addOverlay(overlay);
3874
        if (cm.showMatchesOnScrollbar) {
3875
          if (searchState.getScrollbarAnnotate()) {
3876
            searchState.getScrollbarAnnotate().clear();
3877
          }
3878
          searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query));
3879
        }
3880
        searchState.setOverlay(overlay);
3881
      }
3882
    }
3883
    function findNext(cm, prev, query, repeat) {
3884
      if (repeat === undefined) { repeat = 1; }
3885
      return cm.operation(function() {
3886
        var pos = cm.getCursor();
3887
        var cursor = cm.getSearchCursor(query, pos);
3888
        for (var i = 0; i < repeat; i++) {
3889
          var found = cursor.find(prev);
3890
          if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); }
0 ignored issues
show
Best Practice introduced by
Comparing i to 0 using the == operator is not safe. Consider using === instead.
Loading history...
3891
          if (!found) {
3892
            // SearchCursor may have returned null because it hit EOF, wrap
3893
            // around and try again.
3894
            cursor = cm.getSearchCursor(query,
3895
                (prev) ? Pos(cm.lastLine()) : Pos(cm.firstLine(), 0) );
3896
            if (!cursor.find(prev)) {
3897
              return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
3898
            }
3899
          }
3900
        }
3901
        return cursor.from();
3902
      });
3903
    }
3904
    function clearSearchHighlight(cm) {
3905
      var state = getSearchState(cm);
3906
      cm.removeOverlay(getSearchState(cm).getOverlay());
3907
      state.setOverlay(null);
3908
      if (state.getScrollbarAnnotate()) {
3909
        state.getScrollbarAnnotate().clear();
3910
        state.setScrollbarAnnotate(null);
3911
      }
3912
    }
3913
    /**
3914
     * Check if pos is in the specified range, INCLUSIVE.
3915
     * Range can be specified with 1 or 2 arguments.
3916
     * If the first range argument is an array, treat it as an array of line
3917
     * numbers. Match pos against any of the lines.
3918
     * If the first range argument is a number,
3919
     *   if there is only 1 range argument, check if pos has the same line
3920
     *       number
3921
     *   if there are 2 range arguments, then check if pos is in between the two
3922
     *       range arguments.
3923
     */
3924
    function isInRange(pos, start, end) {
3925
      if (typeof pos != 'number') {
3926
        // Assume it is a cursor position. Get the line number.
3927
        pos = pos.line;
3928
      }
3929
      if (start instanceof Array) {
3930
        return inArray(pos, start);
3931
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
3932
        if (end) {
3933
          return (pos >= start && pos <= end);
3934
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
3935
          return pos == start;
3936
        }
3937
      }
3938
    }
3939
    function getUserVisibleLines(cm) {
3940
      var scrollInfo = cm.getScrollInfo();
3941
      var occludeToleranceTop = 6;
3942
      var occludeToleranceBottom = 10;
3943
      var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
3944
      var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
3945
      var to = cm.coordsChar({left:0, top: bottomY}, 'local');
3946
      return {top: from.line, bottom: to.line};
3947
    }
3948
3949
    var ExCommandDispatcher = function() {
3950
      this.buildCommandMap_();
3951
    };
3952
    ExCommandDispatcher.prototype = {
3953
      processCommand: function(cm, input, opt_params) {
3954
        var that = this;
3955
        cm.operation(function () {
3956
          cm.curOp.isVimOp = true;
3957
          that._processCommand(cm, input, opt_params);
3958
        });
3959
      },
3960
      _processCommand: function(cm, input, opt_params) {
3961
        var vim = cm.state.vim;
3962
        var commandHistoryRegister = vimGlobalState.registerController.getRegister(':');
3963
        var previousCommand = commandHistoryRegister.toString();
3964
        if (vim.visualMode) {
3965
          exitVisualMode(cm);
3966
        }
3967
        var inputStream = new CodeMirror.StringStream(input);
3968
        // update ": with the latest command whether valid or invalid
3969
        commandHistoryRegister.setText(input);
3970
        var params = opt_params || {};
3971
        params.input = input;
3972
        try {
3973
          this.parseInput_(cm, inputStream, params);
3974
        } catch(e) {
3975
          showConfirm(cm, e);
3976
          throw e;
3977
        }
3978
        var command;
3979
        var commandName;
3980
        if (!params.commandName) {
3981
          // If only a line range is defined, move to the line.
3982
          if (params.line !== undefined) {
3983
            commandName = 'move';
3984
          }
3985
        } else {
3986
          command = this.matchCommand_(params.commandName);
3987
          if (command) {
3988
            commandName = command.name;
3989
            if (command.excludeFromCommandHistory) {
3990
              commandHistoryRegister.setText(previousCommand);
3991
            }
3992
            this.parseCommandArgs_(inputStream, params, command);
3993
            if (command.type == 'exToKey') {
3994
              // Handle Ex to Key mapping.
3995
              for (var i = 0; i < command.toKeys.length; i++) {
3996
                CodeMirror.Vim.handleKey(cm, command.toKeys[i], 'mapping');
3997
              }
3998
              return;
3999
            } else if (command.type == 'exToEx') {
4000
              // Handle Ex to Ex mapping.
4001
              this.processCommand(cm, command.toInput);
4002
              return;
4003
            }
4004
          }
4005
        }
4006
        if (!commandName) {
4007
          showConfirm(cm, 'Not an editor command ":' + input + '"');
4008
          return;
4009
        }
4010
        try {
4011
          exCommands[commandName](cm, params);
4012
          // Possibly asynchronous commands (e.g. substitute, which might have a
4013
          // user confirmation), are responsible for calling the callback when
4014
          // done. All others have it taken care of for them here.
4015
          if ((!command || !command.possiblyAsync) && params.callback) {
4016
            params.callback();
4017
          }
4018
        } catch(e) {
4019
          showConfirm(cm, e);
4020
          throw e;
4021
        }
4022
      },
4023
      parseInput_: function(cm, inputStream, result) {
4024
        inputStream.eatWhile(':');
4025
        // Parse range.
4026
        if (inputStream.eat('%')) {
4027
          result.line = cm.firstLine();
4028
          result.lineEnd = cm.lastLine();
4029
        } else {
4030
          result.line = this.parseLineSpec_(cm, inputStream);
4031
          if (result.line !== undefined && inputStream.eat(',')) {
4032
            result.lineEnd = this.parseLineSpec_(cm, inputStream);
4033
          }
4034
        }
4035
4036
        // Parse command name.
4037
        var commandMatch = inputStream.match(/^(\w+)/);
4038
        if (commandMatch) {
4039
          result.commandName = commandMatch[1];
4040
        } else {
4041
          result.commandName = inputStream.match(/.*/)[0];
4042
        }
4043
4044
        return result;
4045
      },
4046
      parseLineSpec_: function(cm, inputStream) {
4047
        var numberMatch = inputStream.match(/^(\d+)/);
4048
        if (numberMatch) {
4049
          return parseInt(numberMatch[1], 10) - 1;
4050
        }
4051
        switch (inputStream.next()) {
4052
          case '.':
4053
            return cm.getCursor().line;
4054
          case '$':
4055
            return cm.lastLine();
4056
          case '\'':
4057
            var mark = cm.state.vim.marks[inputStream.next()];
4058
            if (mark && mark.find()) {
4059
              return mark.find().line;
4060
            }
4061
            throw new Error('Mark not set');
4062
          default:
4063
            inputStream.backUp(1);
4064
            return undefined;
4065
        }
4066
      },
4067
      parseCommandArgs_: function(inputStream, params, command) {
4068
        if (inputStream.eol()) {
4069
          return;
4070
        }
4071
        params.argString = inputStream.match(/.*/)[0];
4072
        // Parse command-line arguments
4073
        var delim = command.argDelimiter || /\s+/;
4074
        var args = trim(params.argString).split(delim);
4075
        if (args.length && args[0]) {
4076
          params.args = args;
4077
        }
4078
      },
4079
      matchCommand_: function(commandName) {
4080
        // Return the command in the command map that matches the shortest
4081
        // prefix of the passed in command name. The match is guaranteed to be
4082
        // unambiguous if the defaultExCommandMap's shortNames are set up
4083
        // correctly. (see @code{defaultExCommandMap}).
4084
        for (var i = commandName.length; i > 0; i--) {
4085
          var prefix = commandName.substring(0, i);
4086
          if (this.commandMap_[prefix]) {
4087
            var command = this.commandMap_[prefix];
4088
            if (command.name.indexOf(commandName) === 0) {
4089
              return command;
4090
            }
4091
          }
4092
        }
4093
        return null;
4094
      },
4095
      buildCommandMap_: function() {
4096
        this.commandMap_ = {};
4097
        for (var i = 0; i < defaultExCommandMap.length; i++) {
4098
          var command = defaultExCommandMap[i];
4099
          var key = command.shortName || command.name;
4100
          this.commandMap_[key] = command;
4101
        }
4102
      },
4103
      map: function(lhs, rhs, ctx) {
4104
        if (lhs != ':' && lhs.charAt(0) == ':') {
4105
          if (ctx) { throw Error('Mode not supported for ex mappings'); }
4106
          var commandName = lhs.substring(1);
4107
          if (rhs != ':' && rhs.charAt(0) == ':') {
4108
            // Ex to Ex mapping
4109
            this.commandMap_[commandName] = {
4110
              name: commandName,
4111
              type: 'exToEx',
4112
              toInput: rhs.substring(1),
4113
              user: true
4114
            };
4115
          } else {
4116
            // Ex to key mapping
4117
            this.commandMap_[commandName] = {
4118
              name: commandName,
4119
              type: 'exToKey',
4120
              toKeys: rhs,
4121
              user: true
4122
            };
4123
          }
4124
        } else {
4125
          if (rhs != ':' && rhs.charAt(0) == ':') {
4126
            // Key to Ex mapping.
4127
            var mapping = {
4128
              keys: lhs,
4129
              type: 'keyToEx',
4130
              exArgs: { input: rhs.substring(1) },
4131
              user: true};
4132
            if (ctx) { mapping.context = ctx; }
4133
            defaultKeymap.unshift(mapping);
4134
          } else {
4135
            // Key to key mapping
4136
            var mapping = {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable mapping already seems to be declared on line 4127. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
4137
              keys: lhs,
4138
              type: 'keyToKey',
4139
              toKeys: rhs,
4140
              user: true
4141
            };
4142
            if (ctx) { mapping.context = ctx; }
4143
            defaultKeymap.unshift(mapping);
4144
          }
4145
        }
4146
      },
4147
      unmap: function(lhs, ctx) {
4148
        if (lhs != ':' && lhs.charAt(0) == ':') {
4149
          // Ex to Ex or Ex to key mapping
4150
          if (ctx) { throw Error('Mode not supported for ex mappings'); }
4151
          var commandName = lhs.substring(1);
4152
          if (this.commandMap_[commandName] && this.commandMap_[commandName].user) {
4153
            delete this.commandMap_[commandName];
4154
            return;
4155
          }
4156
        } else {
4157
          // Key to Ex or key to key mapping
4158
          var keys = lhs;
4159
          for (var i = 0; i < defaultKeymap.length; i++) {
4160
            if (keys == defaultKeymap[i].keys
4161
                && defaultKeymap[i].context === ctx
4162
                && defaultKeymap[i].user) {
4163
              defaultKeymap.splice(i, 1);
4164
              return;
4165
            }
4166
          }
4167
        }
4168
        throw Error('No such mapping.');
4169
      }
4170
    };
4171
4172
    var exCommands = {
4173
      colorscheme: function(cm, params) {
4174
        if (!params.args || params.args.length < 1) {
4175
          showConfirm(cm, cm.getOption('theme'));
4176
          return;
4177
        }
4178
        cm.setOption('theme', params.args[0]);
4179
      },
4180
      map: function(cm, params, ctx) {
4181
        var mapArgs = params.args;
4182
        if (!mapArgs || mapArgs.length < 2) {
4183
          if (cm) {
4184
            showConfirm(cm, 'Invalid mapping: ' + params.input);
4185
          }
4186
          return;
4187
        }
4188
        exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx);
4189
      },
4190
      imap: function(cm, params) { this.map(cm, params, 'insert'); },
4191
      nmap: function(cm, params) { this.map(cm, params, 'normal'); },
4192
      vmap: function(cm, params) { this.map(cm, params, 'visual'); },
4193
      unmap: function(cm, params, ctx) {
4194
        var mapArgs = params.args;
4195
        if (!mapArgs || mapArgs.length < 1) {
4196
          if (cm) {
4197
            showConfirm(cm, 'No such mapping: ' + params.input);
4198
          }
4199
          return;
4200
        }
4201
        exCommandDispatcher.unmap(mapArgs[0], ctx);
4202
      },
4203
      move: function(cm, params) {
4204
        commandDispatcher.processCommand(cm, cm.state.vim, {
4205
            type: 'motion',
4206
            motion: 'moveToLineOrEdgeOfDocument',
4207
            motionArgs: { forward: false, explicitRepeat: true,
4208
              linewise: true },
4209
            repeatOverride: params.line+1});
4210
      },
4211
      set: function(cm, params) {
4212
        var setArgs = params.args;
4213
        // Options passed through to the setOption/getOption calls. May be passed in by the
4214
        // local/global versions of the set command
4215
        var setCfg = params.setCfg || {};
4216
        if (!setArgs || setArgs.length < 1) {
4217
          if (cm) {
4218
            showConfirm(cm, 'Invalid mapping: ' + params.input);
4219
          }
4220
          return;
4221
        }
4222
        var expr = setArgs[0].split('=');
4223
        var optionName = expr[0];
4224
        var value = expr[1];
4225
        var forceGet = false;
4226
4227
        if (optionName.charAt(optionName.length - 1) == '?') {
4228
          // If post-fixed with ?, then the set is actually a get.
4229
          if (value) { throw Error('Trailing characters: ' + params.argString); }
4230
          optionName = optionName.substring(0, optionName.length - 1);
4231
          forceGet = true;
4232
        }
4233
        if (value === undefined && optionName.substring(0, 2) == 'no') {
4234
          // To set boolean options to false, the option name is prefixed with
4235
          // 'no'.
4236
          optionName = optionName.substring(2);
4237
          value = false;
4238
        }
4239
4240
        var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean';
4241
        if (optionIsBoolean && value == undefined) {
0 ignored issues
show
Best Practice introduced by
Comparing value to undefined using the == operator is not safe. Consider using === instead.
Loading history...
4242
          // Calling set with a boolean option sets it to true.
4243
          value = true;
4244
        }
4245
        // If no value is provided, then we assume this is a get.
4246
        if (!optionIsBoolean && value === undefined || forceGet) {
4247
          var oldValue = getOption(optionName, cm, setCfg);
4248
          if (oldValue === true || oldValue === false) {
4249
            showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName);
4250
          } else {
4251
            showConfirm(cm, '  ' + optionName + '=' + oldValue);
4252
          }
4253
        } else {
4254
          setOption(optionName, value, cm, setCfg);
4255
        }
4256
      },
4257
      setlocal: function (cm, params) {
4258
        // setCfg is passed through to setOption
4259
        params.setCfg = {scope: 'local'};
4260
        this.set(cm, params);
4261
      },
4262
      setglobal: function (cm, params) {
4263
        // setCfg is passed through to setOption
4264
        params.setCfg = {scope: 'global'};
4265
        this.set(cm, params);
4266
      },
4267
      registers: function(cm, params) {
4268
        var regArgs = params.args;
4269
        var registers = vimGlobalState.registerController.registers;
4270
        var regInfo = '----------Registers----------<br><br>';
4271
        if (!regArgs) {
4272
          for (var registerName in registers) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
4273
            var text = registers[registerName].toString();
4274
            if (text.length) {
4275
              regInfo += '"' + registerName + '    ' + text + '<br>';
4276
            }
4277
          }
4278
        } else {
4279
          var registerName;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable registerName already seems to be declared on line 4272. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
4280
          regArgs = regArgs.join('');
4281
          for (var i = 0; i < regArgs.length; i++) {
4282
            registerName = regArgs.charAt(i);
4283
            if (!vimGlobalState.registerController.isValidRegister(registerName)) {
4284
              continue;
4285
            }
4286
            var register = registers[registerName] || new Register();
4287
            regInfo += '"' + registerName + '    ' + register.toString() + '<br>';
4288
          }
4289
        }
4290
        showConfirm(cm, regInfo);
4291
      },
4292
      sort: function(cm, params) {
4293
        var reverse, ignoreCase, unique, number;
4294
        function parseArgs() {
4295
          if (params.argString) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if params.argString is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
4296
            var args = new CodeMirror.StringStream(params.argString);
4297
            if (args.eat('!')) { reverse = true; }
4298
            if (args.eol()) { return; }
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
4299
            if (!args.eatSpace()) { return 'Invalid arguments'; }
4300
            var opts = args.match(/[a-z]+/);
4301
            if (opts) {
4302
              opts = opts[0];
4303
              ignoreCase = opts.indexOf('i') != -1;
4304
              unique = opts.indexOf('u') != -1;
4305
              var decimal = opts.indexOf('d') != -1 && 1;
4306
              var hex = opts.indexOf('x') != -1 && 1;
4307
              var octal = opts.indexOf('o') != -1 && 1;
4308
              if (decimal + hex + octal > 1) { return 'Invalid arguments'; }
4309
              number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
4310
            }
4311
            if (args.match(/\/.*\//)) { return 'patterns not supported'; }
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if args.match(\/.*\/) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
4312
          }
4313
        }
4314
        var err = parseArgs();
4315
        if (err) {
4316
          showConfirm(cm, err + ': ' + params.argString);
4317
          return;
4318
        }
4319
        var lineStart = params.line || cm.firstLine();
4320
        var lineEnd = params.lineEnd || params.line || cm.lastLine();
4321
        if (lineStart == lineEnd) { return; }
4322
        var curStart = Pos(lineStart, 0);
4323
        var curEnd = Pos(lineEnd, lineLength(cm, lineEnd));
4324
        var text = cm.getRange(curStart, curEnd).split('\n');
4325
        var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ :
4326
           (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :
4327
           (number == 'octal') ? /([0-7]+)/ : null;
4328
        var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;
4329
        var numPart = [], textPart = [];
4330
        if (number) {
4331
          for (var i = 0; i < text.length; i++) {
4332
            if (numberRegex.exec(text[i])) {
4333
              numPart.push(text[i]);
4334
            } else {
4335
              textPart.push(text[i]);
4336
            }
4337
          }
4338
        } else {
4339
          textPart = text;
4340
        }
4341
        function compareFn(a, b) {
4342
          if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
4343
          if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }
4344
          var anum = number && numberRegex.exec(a);
4345
          var bnum = number && numberRegex.exec(b);
4346
          if (!anum) { return a < b ? -1 : 1; }
4347
          anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);
4348
          bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);
4349
          return anum - bnum;
4350
        }
4351
        numPart.sort(compareFn);
4352
        textPart.sort(compareFn);
4353
        text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);
4354
        if (unique) { // Remove duplicate lines
4355
          var textOld = text;
4356
          var lastLine;
4357
          text = [];
4358
          for (var i = 0; i < textOld.length; i++) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 4331. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
4359
            if (textOld[i] != lastLine) {
0 ignored issues
show
Bug introduced by
The variable lastLine seems to not be initialized for all possible execution paths.
Loading history...
4360
              text.push(textOld[i]);
4361
            }
4362
            lastLine = textOld[i];
4363
          }
4364
        }
4365
        cm.replaceRange(text.join('\n'), curStart, curEnd);
4366
      },
4367
      global: function(cm, params) {
4368
        // a global command is of the form
4369
        // :[range]g/pattern/[cmd]
4370
        // argString holds the string /pattern/[cmd]
4371
        var argString = params.argString;
4372
        if (!argString) {
4373
          showConfirm(cm, 'Regular Expression missing from global');
4374
          return;
4375
        }
4376
        // range is specified here
4377
        var lineStart = (params.line !== undefined) ? params.line : cm.firstLine();
4378
        var lineEnd = params.lineEnd || params.line || cm.lastLine();
4379
        // get the tokens from argString
4380
        var tokens = splitBySlash(argString);
4381
        var regexPart = argString, cmd;
4382
        if (tokens.length) {
4383
          regexPart = tokens[0];
4384
          cmd = tokens.slice(1, tokens.length).join('/');
4385
        }
4386
        if (regexPart) {
4387
          // If regex part is empty, then use the previous query. Otherwise
4388
          // use the regex part as the new query.
4389
          try {
4390
           updateSearchQuery(cm, regexPart, true /** ignoreCase */,
4391
             true /** smartCase */);
4392
          } catch (e) {
4393
           showConfirm(cm, 'Invalid regex: ' + regexPart);
4394
           return;
4395
          }
4396
        }
4397
        // now that we have the regexPart, search for regex matches in the
4398
        // specified range of lines
4399
        var query = getSearchState(cm).getQuery();
4400
        var matchedLines = [], content = '';
4401
        for (var i = lineStart; i <= lineEnd; i++) {
4402
          var matched = query.test(cm.getLine(i));
4403
          if (matched) {
4404
            matchedLines.push(i+1);
4405
            content+= cm.getLine(i) + '<br>';
4406
          }
4407
        }
4408
        // if there is no [cmd], just display the list of matched lines
4409
        if (!cmd) {
4410
          showConfirm(cm, content);
4411
          return;
4412
        }
4413
        var index = 0;
4414
        var nextCommand = function() {
4415
          if (index < matchedLines.length) {
4416
            var command = matchedLines[index] + cmd;
4417
            exCommandDispatcher.processCommand(cm, command, {
4418
              callback: nextCommand
4419
            });
4420
          }
4421
          index++;
4422
        };
4423
        nextCommand();
4424
      },
4425
      substitute: function(cm, params) {
4426
        if (!cm.getSearchCursor) {
4427
          throw new Error('Search feature not available. Requires searchcursor.js or ' +
4428
              'any other getSearchCursor implementation.');
4429
        }
4430
        var argString = params.argString;
4431
        var tokens = argString ? splitBySlash(argString) : [];
4432
        var regexPart, replacePart = '', trailing, flagsPart, count;
4433
        var confirm = false; // Whether to confirm each replace.
4434
        var global = false; // True to replace all instances on a line, false to replace only 1.
4435
        if (tokens.length) {
4436
          regexPart = tokens[0];
4437
          replacePart = tokens[1];
4438
          if (replacePart !== undefined) {
4439
            if (getOption('pcre')) {
4440
              replacePart = unescapeRegexReplace(replacePart);
4441
            } else {
4442
              replacePart = translateRegexReplace(replacePart);
4443
            }
4444
            vimGlobalState.lastSubstituteReplacePart = replacePart;
4445
          }
4446
          trailing = tokens[2] ? tokens[2].split(' ') : [];
4447
        } else {
4448
          // either the argString is empty or its of the form ' hello/world'
4449
          // actually splitBySlash returns a list of tokens
4450
          // only if the string starts with a '/'
4451
          if (argString && argString.length) {
4452
            showConfirm(cm, 'Substitutions should be of the form ' +
4453
                ':s/pattern/replace/');
4454
            return;
4455
          }
4456
        }
4457
        // After the 3rd slash, we can have flags followed by a space followed
4458
        // by count.
4459
        if (trailing) {
4460
          flagsPart = trailing[0];
4461
          count = parseInt(trailing[1]);
4462
          if (flagsPart) {
4463
            if (flagsPart.indexOf('c') != -1) {
4464
              confirm = true;
4465
              flagsPart.replace('c', '');
4466
            }
4467
            if (flagsPart.indexOf('g') != -1) {
4468
              global = true;
4469
              flagsPart.replace('g', '');
4470
            }
4471
            regexPart = regexPart + '/' + flagsPart;
0 ignored issues
show
Bug introduced by
The variable regexPart does not seem to be initialized in case argString && argString.length on line 4451 is false. Are you sure this can never be the case?
Loading history...
4472
          }
4473
        }
4474
        if (regexPart) {
4475
          // If regex part is empty, then use the previous query. Otherwise use
4476
          // the regex part as the new query.
4477
          try {
4478
            updateSearchQuery(cm, regexPart, true /** ignoreCase */,
4479
              true /** smartCase */);
4480
          } catch (e) {
4481
            showConfirm(cm, 'Invalid regex: ' + regexPart);
4482
            return;
4483
          }
4484
        }
4485
        replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart;
4486
        if (replacePart === undefined) {
4487
          showConfirm(cm, 'No previous substitute regular expression');
4488
          return;
4489
        }
4490
        var state = getSearchState(cm);
4491
        var query = state.getQuery();
4492
        var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
4493
        var lineEnd = params.lineEnd || lineStart;
4494
        if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) {
4495
          lineEnd = Infinity;
0 ignored issues
show
Comprehensibility Best Practice introduced by
You seem to be aliasing the built-in name Infinity as lineEnd. This makes your code very difficult to follow, consider using the built-in name directly.
Loading history...
4496
        }
4497
        if (count) {
4498
          lineStart = lineEnd;
4499
          lineEnd = lineStart + count - 1;
4500
        }
4501
        var startPos = clipCursorToContent(cm, Pos(lineStart, 0));
4502
        var cursor = cm.getSearchCursor(query, startPos);
4503
        doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback);
4504
      },
4505
      redo: CodeMirror.commands.redo,
4506
      undo: CodeMirror.commands.undo,
4507
      write: function(cm) {
4508
        if (CodeMirror.commands.save) {
4509
          // If a save command is defined, call it.
4510
          CodeMirror.commands.save(cm);
4511
        } else {
4512
          // Saves to text area if no save command is defined.
4513
          cm.save();
4514
        }
4515
      },
4516
      nohlsearch: function(cm) {
4517
        clearSearchHighlight(cm);
4518
      },
4519
      delmarks: function(cm, params) {
4520
        if (!params.argString || !trim(params.argString)) {
4521
          showConfirm(cm, 'Argument required');
4522
          return;
4523
        }
4524
4525
        var state = cm.state.vim;
4526
        var stream = new CodeMirror.StringStream(trim(params.argString));
4527
        while (!stream.eol()) {
4528
          stream.eatSpace();
4529
4530
          // Record the streams position at the beginning of the loop for use
4531
          // in error messages.
4532
          var count = stream.pos;
4533
4534
          if (!stream.match(/[a-zA-Z]/, false)) {
4535
            showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
4536
            return;
4537
          }
4538
4539
          var sym = stream.next();
4540
          // Check if this symbol is part of a range
4541
          if (stream.match('-', true)) {
4542
            // This symbol is part of a range.
4543
4544
            // The range must terminate at an alphabetic character.
4545
            if (!stream.match(/[a-zA-Z]/, false)) {
4546
              showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
4547
              return;
4548
            }
4549
4550
            var startMark = sym;
4551
            var finishMark = stream.next();
4552
            // The range must terminate at an alphabetic character which
4553
            // shares the same case as the start of the range.
4554
            if (isLowerCase(startMark) && isLowerCase(finishMark) ||
4555
                isUpperCase(startMark) && isUpperCase(finishMark)) {
4556
              var start = startMark.charCodeAt(0);
4557
              var finish = finishMark.charCodeAt(0);
4558
              if (start >= finish) {
4559
                showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
4560
                return;
4561
              }
4562
4563
              // Because marks are always ASCII values, and we have
4564
              // determined that they are the same case, we can use
4565
              // their char codes to iterate through the defined range.
4566
              for (var j = 0; j <= finish - start; j++) {
4567
                var mark = String.fromCharCode(start + j);
4568
                delete state.marks[mark];
4569
              }
4570
            } else {
4571
              showConfirm(cm, 'Invalid argument: ' + startMark + '-');
4572
              return;
4573
            }
4574
          } else {
4575
            // This symbol is a valid mark, and is not part of a range.
4576
            delete state.marks[sym];
4577
          }
4578
        }
4579
      }
4580
    };
4581
4582
    var exCommandDispatcher = new ExCommandDispatcher();
4583
4584
    /**
4585
    * @param {CodeMirror} cm CodeMirror instance we are in.
4586
    * @param {boolean} confirm Whether to confirm each replace.
4587
    * @param {Cursor} lineStart Line to start replacing from.
4588
    * @param {Cursor} lineEnd Line to stop replacing at.
4589
    * @param {RegExp} query Query for performing matches with.
4590
    * @param {string} replaceWith Text to replace matches with. May contain $1,
4591
    *     $2, etc for replacing captured groups using Javascript replace.
4592
    * @param {function()} callback A callback for when the replace is done.
4593
    */
4594
    function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query,
4595
        replaceWith, callback) {
4596
      // Set up all the functions.
4597
      cm.state.vim.exMode = true;
4598
      var done = false;
4599
      var lastPos = searchCursor.from();
4600
      function replaceAll() {
4601
        cm.operation(function() {
4602
          while (!done) {
4603
            replace();
4604
            next();
4605
          }
4606
          stop();
4607
        });
4608
      }
4609
      function replace() {
4610
        var text = cm.getRange(searchCursor.from(), searchCursor.to());
4611
        var newText = text.replace(query, replaceWith);
4612
        searchCursor.replace(newText);
4613
      }
4614
      function next() {
4615
        // The below only loops to skip over multiple occurrences on the same
4616
        // line when 'global' is not true.
4617
        while(searchCursor.findNext() &&
4618
              isInRange(searchCursor.from(), lineStart, lineEnd)) {
4619
          if (!global && lastPos && searchCursor.from().line == lastPos.line) {
4620
            continue;
4621
          }
4622
          cm.scrollIntoView(searchCursor.from(), 30);
4623
          cm.setSelection(searchCursor.from(), searchCursor.to());
4624
          lastPos = searchCursor.from();
4625
          done = false;
4626
          return;
4627
        }
4628
        done = true;
4629
      }
4630
      function stop(close) {
4631
        if (close) { close(); }
4632
        cm.focus();
4633
        if (lastPos) {
4634
          cm.setCursor(lastPos);
4635
          var vim = cm.state.vim;
4636
          vim.exMode = false;
4637
          vim.lastHPos = vim.lastHSPos = lastPos.ch;
4638
        }
4639
        if (callback) { callback(); }
4640
      }
4641
      function onPromptKeyDown(e, _value, close) {
4642
        // Swallow all keys.
4643
        CodeMirror.e_stop(e);
4644
        var keyName = CodeMirror.keyName(e);
4645
        switch (keyName) {
4646
          case 'Y':
4647
            replace(); next(); break;
4648
          case 'N':
4649
            next(); break;
4650
          case 'A':
4651
            // replaceAll contains a call to close of its own. We don't want it
4652
            // to fire too early or multiple times.
4653
            var savedCallback = callback;
4654
            callback = undefined;
4655
            cm.operation(replaceAll);
4656
            callback = savedCallback;
4657
            break;
4658
          case 'L':
4659
            replace();
4660
            // fall through and exit.
4661
          case 'Q':
4662
          case 'Esc':
4663
          case 'Ctrl-C':
4664
          case 'Ctrl-[':
4665
            stop(close);
4666
            break;
4667
        }
4668
        if (done) { stop(close); }
4669
        return true;
4670
      }
4671
4672
      // Actually do replace.
4673
      next();
4674
      if (done) {
4675
        showConfirm(cm, 'No matches for ' + query.source);
4676
        return;
4677
      }
4678
      if (!confirm) {
4679
        replaceAll();
4680
        if (callback) { callback(); };
4681
        return;
4682
      }
4683
      showPrompt(cm, {
4684
        prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)',
4685
        onKeyDown: onPromptKeyDown
4686
      });
4687
    }
4688
4689
    CodeMirror.keyMap.vim = {
4690
      attach: attachVimMap,
4691
      detach: detachVimMap,
4692
      call: cmKey
4693
    };
4694
4695
    function exitInsertMode(cm) {
4696
      var vim = cm.state.vim;
4697
      var macroModeState = vimGlobalState.macroModeState;
4698
      var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');
4699
      var isPlaying = macroModeState.isPlaying;
4700
      var lastChange = macroModeState.lastInsertModeChanges;
4701
      // In case of visual block, the insertModeChanges are not saved as a
4702
      // single word, so we convert them to a single word
4703
      // so as to update the ". register as expected in real vim.
4704
      var text = [];
0 ignored issues
show
Unused Code introduced by
The assignment to variable text seems to be never used. Consider removing it.
Loading history...
4705
      if (!isPlaying) {
4706
        var selLength = lastChange.inVisualBlock ? vim.lastSelection.visualBlock.height : 1;
4707
        var changes = lastChange.changes;
4708
        var text = [];
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable text already seems to be declared on line 4704. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
4709
        var i = 0;
4710
        // In case of multiple selections in blockwise visual,
4711
        // the inserted text, for example: 'f<Backspace>oo', is stored as
4712
        // 'f', 'f', InsertModeKey 'o', 'o', 'o', 'o'. (if you have a block with 2 lines).
4713
        // We push the contents of the changes array as per the following:
4714
        // 1. In case of InsertModeKey, just increment by 1.
4715
        // 2. In case of a character, jump by selLength (2 in the example).
4716
        while (i < changes.length) {
4717
          // This loop will convert 'ff<bs>oooo' to 'f<bs>oo'.
4718
          text.push(changes[i]);
4719
          if (changes[i] instanceof InsertModeKey) {
4720
             i++;
4721
          } else {
4722
             i+= selLength;
4723
          }
4724
        }
4725
        lastChange.changes = text;
4726
        cm.off('change', onChange);
4727
        CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
4728
      }
4729
      if (!isPlaying && vim.insertModeRepeat > 1) {
4730
        // Perform insert mode repeat for commands like 3,a and 3,o.
4731
        repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
4732
            true /** repeatForInsert */);
4733
        vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;
4734
      }
4735
      delete vim.insertModeRepeat;
4736
      vim.insertMode = false;
4737
      cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1);
4738
      cm.setOption('keyMap', 'vim');
4739
      cm.setOption('disableInput', true);
4740
      cm.toggleOverwrite(false); // exit replace mode if we were in it.
4741
      // update the ". register before exiting insert mode
4742
      insertModeChangeRegister.setText(lastChange.changes.join(''));
4743
      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
4744
      if (macroModeState.isRecording) {
4745
        logInsertModeChange(macroModeState);
4746
      }
4747
    }
4748
4749
    function _mapCommand(command) {
4750
      defaultKeymap.unshift(command);
4751
    }
4752
4753
    function mapCommand(keys, type, name, args, extra) {
4754
      var command = {keys: keys, type: type};
4755
      command[type] = name;
4756
      command[type + "Args"] = args;
4757
      for (var key in extra)
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
4758
        command[key] = extra[key];
4759
      _mapCommand(command);
4760
    }
4761
4762
    // The timeout in milliseconds for the two-character ESC keymap should be
4763
    // adjusted according to your typing speed to prevent false positives.
4764
    defineOption('insertModeEscKeysTimeout', 200, 'number');
4765
4766
    CodeMirror.keyMap['vim-insert'] = {
4767
      // TODO: override navigation keys so that Esc will cancel automatic
4768
      // indentation from o, O, i_<CR>
4769
      'Ctrl-N': 'autocomplete',
4770
      'Ctrl-P': 'autocomplete',
4771
      'Enter': function(cm) {
4772
        var fn = CodeMirror.commands.newlineAndIndentContinueComment ||
4773
            CodeMirror.commands.newlineAndIndent;
4774
        fn(cm);
4775
      },
4776
      fallthrough: ['default'],
4777
      attach: attachVimMap,
4778
      detach: detachVimMap,
4779
      call: cmKey
4780
    };
4781
4782
    CodeMirror.keyMap['vim-replace'] = {
4783
      'Backspace': 'goCharLeft',
4784
      fallthrough: ['vim-insert'],
4785
      attach: attachVimMap,
4786
      detach: detachVimMap,
4787
      call: cmKey
4788
    };
4789
4790
    function executeMacroRegister(cm, vim, macroModeState, registerName) {
4791
      var register = vimGlobalState.registerController.getRegister(registerName);
4792
      if (registerName == ':') {
4793
        // Read-only register containing last Ex command.
4794
        if (register.keyBuffer[0]) {
4795
          exCommandDispatcher.processCommand(cm, register.keyBuffer[0]);
4796
        }
4797
        macroModeState.isPlaying = false;
4798
        return;
4799
      }
4800
      var keyBuffer = register.keyBuffer;
4801
      var imc = 0;
4802
      macroModeState.isPlaying = true;
4803
      macroModeState.replaySearchQueries = register.searchQueries.slice(0);
4804
      for (var i = 0; i < keyBuffer.length; i++) {
4805
        var text = keyBuffer[i];
4806
        var match, key;
4807
        while (text) {
4808
          // Pull off one command key, which is either a single character
4809
          // or a special sequence wrapped in '<' and '>', e.g. '<Space>'.
4810
          match = (/<\w+-.+?>|<\w+>|./).exec(text);
4811
          key = match[0];
4812
          text = text.substring(match.index + key.length);
4813
          CodeMirror.Vim.handleKey(cm, key, 'macro');
4814
          if (vim.insertMode) {
4815
            var changes = register.insertModeChanges[imc++].changes;
4816
            vimGlobalState.macroModeState.lastInsertModeChanges.changes =
4817
                changes;
4818
            repeatInsertModeChanges(cm, changes, 1);
4819
            exitInsertMode(cm);
4820
          }
4821
        }
4822
      };
4823
      macroModeState.isPlaying = false;
4824
    }
4825
4826
    function logKey(macroModeState, key) {
4827
      if (macroModeState.isPlaying) { return; }
4828
      var registerName = macroModeState.latestRegister;
4829
      var register = vimGlobalState.registerController.getRegister(registerName);
4830
      if (register) {
4831
        register.pushText(key);
4832
      }
4833
    }
4834
4835
    function logInsertModeChange(macroModeState) {
4836
      if (macroModeState.isPlaying) { return; }
4837
      var registerName = macroModeState.latestRegister;
4838
      var register = vimGlobalState.registerController.getRegister(registerName);
4839
      if (register && register.pushInsertModeChanges) {
4840
        register.pushInsertModeChanges(macroModeState.lastInsertModeChanges);
4841
      }
4842
    }
4843
4844
    function logSearchQuery(macroModeState, query) {
4845
      if (macroModeState.isPlaying) { return; }
4846
      var registerName = macroModeState.latestRegister;
4847
      var register = vimGlobalState.registerController.getRegister(registerName);
4848
      if (register && register.pushSearchQuery) {
4849
        register.pushSearchQuery(query);
4850
      }
4851
    }
4852
4853
    /**
4854
     * Listens for changes made in insert mode.
4855
     * Should only be active in insert mode.
4856
     */
4857
    function onChange(_cm, changeObj) {
4858
      var macroModeState = vimGlobalState.macroModeState;
4859
      var lastChange = macroModeState.lastInsertModeChanges;
4860
      if (!macroModeState.isPlaying) {
4861
        while(changeObj) {
4862
          lastChange.expectCursorActivityForChange = true;
4863
          if (changeObj.origin == '+input' || changeObj.origin == 'paste'
4864
              || changeObj.origin === undefined /* only in testing */) {
4865
            var text = changeObj.text.join('\n');
4866
            lastChange.changes.push(text);
4867
          }
4868
          // Change objects may be chained with next.
4869
          changeObj = changeObj.next;
4870
        }
4871
      }
4872
    }
4873
4874
    /**
4875
    * Listens for any kind of cursor activity on CodeMirror.
4876
    */
4877
    function onCursorActivity(cm) {
4878
      var vim = cm.state.vim;
4879
      if (vim.insertMode) {
4880
        // Tracking cursor activity in insert mode (for macro support).
4881
        var macroModeState = vimGlobalState.macroModeState;
4882
        if (macroModeState.isPlaying) { return; }
4883
        var lastChange = macroModeState.lastInsertModeChanges;
4884
        if (lastChange.expectCursorActivityForChange) {
4885
          lastChange.expectCursorActivityForChange = false;
4886
        } else {
4887
          // Cursor moved outside the context of an edit. Reset the change.
4888
          lastChange.changes = [];
4889
        }
4890
      } else if (!cm.curOp.isVimOp) {
4891
        handleExternalSelection(cm, vim);
4892
      }
4893
      if (vim.visualMode) {
4894
        updateFakeCursor(cm);
4895
      }
4896
    }
4897
    function updateFakeCursor(cm) {
4898
      var vim = cm.state.vim;
4899
      var from = clipCursorToContent(cm, copyCursor(vim.sel.head));
4900
      var to = offsetCursor(from, 0, 1);
4901
      if (vim.fakeCursor) {
4902
        vim.fakeCursor.clear();
4903
      }
4904
      vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'});
4905
    }
4906
    function handleExternalSelection(cm, vim) {
4907
      var anchor = cm.getCursor('anchor');
4908
      var head = cm.getCursor('head');
4909
      // Enter or exit visual mode to match mouse selection.
4910
      if (vim.visualMode && !cm.somethingSelected()) {
4911
        exitVisualMode(cm, false);
4912
      } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
4913
        vim.visualMode = true;
4914
        vim.visualLine = false;
4915
        CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
4916
      }
4917
      if (vim.visualMode) {
4918
        // Bind CodeMirror selection model to vim selection model.
4919
        // Mouse selections are considered visual characterwise.
4920
        var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0;
4921
        var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0;
4922
        head = offsetCursor(head, 0, headOffset);
4923
        anchor = offsetCursor(anchor, 0, anchorOffset);
4924
        vim.sel = {
4925
          anchor: anchor,
4926
          head: head
4927
        };
4928
        updateMark(cm, vim, '<', cursorMin(head, anchor));
4929
        updateMark(cm, vim, '>', cursorMax(head, anchor));
4930
      } else if (!vim.insertMode) {
4931
        // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse.
4932
        vim.lastHPos = cm.getCursor().ch;
4933
      }
4934
    }
4935
4936
    /** Wrapper for special keys pressed in insert mode */
4937
    function InsertModeKey(keyName) {
4938
      this.keyName = keyName;
4939
    }
4940
4941
    /**
4942
    * Handles raw key down events from the text area.
4943
    * - Should only be active in insert mode.
4944
    * - For recording deletes in insert mode.
4945
    */
4946
    function onKeyEventTargetKeyDown(e) {
4947
      var macroModeState = vimGlobalState.macroModeState;
4948
      var lastChange = macroModeState.lastInsertModeChanges;
4949
      var keyName = CodeMirror.keyName(e);
4950
      if (!keyName) { return; }
4951
      function onKeyFound() {
4952
        lastChange.changes.push(new InsertModeKey(keyName));
4953
        return true;
4954
      }
4955
      if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
4956
        CodeMirror.lookupKey(keyName, 'vim-insert', onKeyFound);
4957
      }
4958
    }
4959
4960
    /**
4961
     * Repeats the last edit, which includes exactly 1 command and at most 1
4962
     * insert. Operator and motion commands are read from lastEditInputState,
4963
     * while action commands are read from lastEditActionCommand.
4964
     *
4965
     * If repeatForInsert is true, then the function was called by
4966
     * exitInsertMode to repeat the insert mode changes the user just made. The
4967
     * corresponding enterInsertMode call was made with a count.
4968
     */
4969
    function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
4970
      var macroModeState = vimGlobalState.macroModeState;
4971
      macroModeState.isPlaying = true;
4972
      var isAction = !!vim.lastEditActionCommand;
4973
      var cachedInputState = vim.inputState;
4974
      function repeatCommand() {
4975
        if (isAction) {
4976
          commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);
4977
        } else {
4978
          commandDispatcher.evalInput(cm, vim);
4979
        }
4980
      }
4981
      function repeatInsert(repeat) {
4982
        if (macroModeState.lastInsertModeChanges.changes.length > 0) {
4983
          // For some reason, repeat cw in desktop VIM does not repeat
4984
          // insert mode changes. Will conform to that behavior.
4985
          repeat = !vim.lastEditActionCommand ? 1 : repeat;
4986
          var changeObject = macroModeState.lastInsertModeChanges;
4987
          repeatInsertModeChanges(cm, changeObject.changes, repeat);
4988
        }
4989
      }
4990
      vim.inputState = vim.lastEditInputState;
4991
      if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {
4992
        // o and O repeat have to be interlaced with insert repeats so that the
4993
        // insertions appear on separate lines instead of the last line.
4994
        for (var i = 0; i < repeat; i++) {
4995
          repeatCommand();
4996
          repeatInsert(1);
4997
        }
4998
      } else {
4999
        if (!repeatForInsert) {
5000
          // Hack to get the cursor to end up at the right place. If I is
5001
          // repeated in insert mode repeat, cursor will be 1 insert
5002
          // change set left of where it should be.
5003
          repeatCommand();
5004
        }
5005
        repeatInsert(repeat);
5006
      }
5007
      vim.inputState = cachedInputState;
5008
      if (vim.insertMode && !repeatForInsert) {
5009
        // Don't exit insert mode twice. If repeatForInsert is set, then we
5010
        // were called by an exitInsertMode call lower on the stack.
5011
        exitInsertMode(cm);
5012
      }
5013
      macroModeState.isPlaying = false;
5014
    };
5015
5016
    function repeatInsertModeChanges(cm, changes, repeat) {
5017
      function keyHandler(binding) {
5018
        if (typeof binding == 'string') {
5019
          CodeMirror.commands[binding](cm);
5020
        } else {
5021
          binding(cm);
5022
        }
5023
        return true;
5024
      }
5025
      var head = cm.getCursor('head');
5026
      var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock;
5027
      if (inVisualBlock) {
5028
        // Set up block selection again for repeating the changes.
5029
        var vim = cm.state.vim;
5030
        var lastSel = vim.lastSelection;
5031
        var offset = getOffset(lastSel.anchor, lastSel.head);
5032
        selectForInsert(cm, head, offset.line + 1);
5033
        repeat = cm.listSelections().length;
5034
        cm.setCursor(head);
5035
      }
5036
      for (var i = 0; i < repeat; i++) {
5037
        if (inVisualBlock) {
5038
          cm.setCursor(offsetCursor(head, i, 0));
5039
        }
5040
        for (var j = 0; j < changes.length; j++) {
5041
          var change = changes[j];
5042
          if (change instanceof InsertModeKey) {
5043
            CodeMirror.lookupKey(change.keyName, 'vim-insert', keyHandler);
5044
          } else {
5045
            var cur = cm.getCursor();
5046
            cm.replaceRange(change, cur, cur);
5047
          }
5048
        }
5049
      }
5050
      if (inVisualBlock) {
5051
        cm.setCursor(offsetCursor(head, 0, 1));
5052
      }
5053
    }
5054
5055
    resetVimGlobalState();
5056
    return vimApi;
5057
  };
5058
  // Initialize Vim and make it available as an API.
5059
  CodeMirror.Vim = Vim();
5060
});
5061