codemirror-rails 4.7 → 4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/lib/codemirror/rails/version.rb +2 -2
  3. data/vendor/assets/javascripts/codemirror.js +229 -137
  4. data/vendor/assets/javascripts/codemirror/addons/edit/closebrackets.js +1 -1
  5. data/vendor/assets/javascripts/codemirror/addons/hint/javascript-hint.js +9 -9
  6. data/vendor/assets/javascripts/codemirror/addons/hint/xml-hint.js +9 -1
  7. data/vendor/assets/javascripts/codemirror/addons/mode/loadmode.js +19 -16
  8. data/vendor/assets/javascripts/codemirror/addons/mode/overlay.js +3 -3
  9. data/vendor/assets/javascripts/codemirror/keymaps/emacs.js +19 -28
  10. data/vendor/assets/javascripts/codemirror/keymaps/sublime.js +14 -15
  11. data/vendor/assets/javascripts/codemirror/keymaps/vim.js +694 -752
  12. data/vendor/assets/javascripts/codemirror/modes/clike.js +15 -0
  13. data/vendor/assets/javascripts/codemirror/modes/css.js +1 -1
  14. data/vendor/assets/javascripts/codemirror/modes/dockerfile.js +80 -0
  15. data/vendor/assets/javascripts/codemirror/modes/gfm.js +2 -1
  16. data/vendor/assets/javascripts/codemirror/modes/htmlmixed.js +2 -2
  17. data/vendor/assets/javascripts/codemirror/modes/idl.js +290 -0
  18. data/vendor/assets/javascripts/codemirror/modes/markdown.js +62 -55
  19. data/vendor/assets/javascripts/codemirror/modes/sparql.js +19 -5
  20. data/vendor/assets/javascripts/codemirror/modes/sql.js +0 -2
  21. data/vendor/assets/javascripts/codemirror/modes/stex.js +184 -193
  22. data/vendor/assets/javascripts/codemirror/modes/yaml.js +6 -1
  23. data/vendor/assets/stylesheets/codemirror.css +11 -2
  24. data/vendor/assets/stylesheets/codemirror/themes/3024-day.css +1 -1
  25. data/vendor/assets/stylesheets/codemirror/themes/solarized.css +0 -5
  26. metadata +3 -1
@@ -71,7 +71,7 @@
71
71
  };
72
72
  var closingBrackets = "";
73
73
  for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
74
- if (left != right) closingBrackets += right;
74
+ closingBrackets += right;
75
75
  map["'" + left + "'"] = function(cm) {
76
76
  if (cm.getOption("disableInput")) return CodeMirror.Pass;
77
77
  var ranges = cm.listSelections(), type, next;
@@ -93,7 +93,7 @@
93
93
  "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
94
94
 
95
95
  function getCompletions(token, context, keywords, options) {
96
- var found = [], start = token.string;
96
+ var found = [], start = token.string, global = options && options.globalScope || window;
97
97
  function maybeAdd(str) {
98
98
  if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
99
99
  }
@@ -112,28 +112,28 @@
112
112
  if (options && options.additionalContext)
113
113
  base = options.additionalContext[obj.string];
114
114
  if (!options || options.useGlobalScope !== false)
115
- base = base || window[obj.string];
115
+ base = base || global[obj.string];
116
116
  } else if (obj.type == "string") {
117
117
  base = "";
118
118
  } else if (obj.type == "atom") {
119
119
  base = 1;
120
120
  } else if (obj.type == "function") {
121
- if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
122
- (typeof window.jQuery == 'function'))
123
- base = window.jQuery();
124
- else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function'))
125
- base = window._();
121
+ if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
122
+ (typeof global.jQuery == 'function'))
123
+ base = global.jQuery();
124
+ else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
125
+ base = global._();
126
126
  }
127
127
  while (base != null && context.length)
128
128
  base = base[context.pop().string];
129
129
  if (base != null) gatherCompletions(base);
130
130
  } else {
131
- // If not, just look in the window object and any local scope
131
+ // If not, just look in the global object and any local scope
132
132
  // (reading into JS mode internals to get at the local and global variables)
133
133
  for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
134
134
  for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
135
135
  if (!options || options.useGlobalScope !== false)
136
- gatherCompletions(window);
136
+ gatherCompletions(global);
137
137
  forEach(keywords, maybeAdd);
138
138
  }
139
139
  return found;
@@ -18,10 +18,17 @@
18
18
  var quote = (options && options.quoteChar) || '"';
19
19
  if (!tags) return;
20
20
  var cur = cm.getCursor(), token = cm.getTokenAt(cur);
21
+ if (/^<\/?$/.test(token.string) && token.end == cur.ch) {
22
+ var nextToken = cm.getTokenAt(Pos(cur.line, cur.ch + 1));
23
+ if (nextToken.start == cur.ch && /\btag\b/.test(nextToken.type))
24
+ token = nextToken;
25
+ }
21
26
  var inner = CodeMirror.innerMode(cm.getMode(), token.state);
22
27
  if (inner.mode.name != "xml") return;
23
28
  var result = [], replaceToken = false, prefix;
24
- var tag = /\btag\b/.test(token.type), tagName = tag && /^\w/.test(token.string), tagStart;
29
+ var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string);
30
+ var tagName = tag && /^\w/.test(token.string), tagStart;
31
+
25
32
  if (tagName) {
26
33
  var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start);
27
34
  var tagType = /<\/$/.test(before) ? "close" : /<$/.test(before) ? "open" : null;
@@ -31,6 +38,7 @@
31
38
  } else if (tag && token.string == "</") {
32
39
  tagType = "close";
33
40
  }
41
+
34
42
  if (!tag && !inner.state.tagName || tagType) {
35
43
  if (tagName)
36
44
  prefix = token.string;
@@ -3,12 +3,12 @@
3
3
 
4
4
  (function(mod) {
5
5
  if (typeof exports == "object" && typeof module == "object") // CommonJS
6
- mod(require("../../lib/codemirror"));
6
+ mod(require("../../lib/codemirror"), "cjs");
7
7
  else if (typeof define == "function" && define.amd) // AMD
8
- define(["../../lib/codemirror"], mod);
8
+ define(["../../lib/codemirror"], function(CM) { mod(CM, "amd"); });
9
9
  else // Plain browser env
10
- mod(CodeMirror);
11
- })(function(CodeMirror) {
10
+ mod(CodeMirror, "plain");
11
+ })(function(CodeMirror, env) {
12
12
  if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js";
13
13
 
14
14
  var loading = {};
@@ -35,21 +35,24 @@
35
35
  if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont);
36
36
  if (loading.hasOwnProperty(mode)) return loading[mode].push(cont);
37
37
 
38
- var script = document.createElement("script");
39
- script.src = CodeMirror.modeURL.replace(/%N/g, mode);
40
- var others = document.getElementsByTagName("script")[0];
41
- others.parentNode.insertBefore(script, others);
42
- var list = loading[mode] = [cont];
43
- var count = 0, poll = setInterval(function() {
44
- if (++count > 100) return clearInterval(poll);
45
- if (CodeMirror.modes.hasOwnProperty(mode)) {
46
- clearInterval(poll);
47
- loading[mode] = null;
38
+ var file = CodeMirror.modeURL.replace(/%N/g, mode);
39
+ if (env == "plain") {
40
+ var script = document.createElement("script");
41
+ script.src = file;
42
+ var others = document.getElementsByTagName("script")[0];
43
+ var list = loading[mode] = [cont];
44
+ CodeMirror.on(script, "load", function() {
48
45
  ensureDeps(mode, function() {
49
46
  for (var i = 0; i < list.length; ++i) list[i]();
50
47
  });
51
- }
52
- }, 200);
48
+ });
49
+ others.parentNode.insertBefore(script, others);
50
+ } else if (env == "cjs") {
51
+ require(file);
52
+ cont();
53
+ } else if (env == "amd") {
54
+ requirejs([file], cont);
55
+ }
53
56
  };
54
57
 
55
58
  CodeMirror.autoLoadMode = function(instance, mode) {
@@ -28,7 +28,7 @@ CodeMirror.overlayMode = function(base, overlay, combine) {
28
28
  overlay: CodeMirror.startState(overlay),
29
29
  basePos: 0, baseCur: null,
30
30
  overlayPos: 0, overlayCur: null,
31
- lineSeen: null
31
+ streamSeen: null
32
32
  };
33
33
  },
34
34
  copyState: function(state) {
@@ -41,9 +41,9 @@ CodeMirror.overlayMode = function(base, overlay, combine) {
41
41
  },
42
42
 
43
43
  token: function(stream, state) {
44
- if (stream.sol() || stream.string != state.lineSeen ||
44
+ if (stream != state.streamSeen ||
45
45
  Math.min(state.basePos, state.overlayPos) < stream.start) {
46
- state.lineSeen = stream.string;
46
+ state.streamSeen = stream;
47
47
  state.basePos = state.overlayPos = stream.start;
48
48
  }
49
49
 
@@ -256,7 +256,7 @@
256
256
 
257
257
  // Actual keymap
258
258
 
259
- var keyMap = CodeMirror.keyMap.emacs = {
259
+ var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({
260
260
  "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));},
261
261
  "Ctrl-K": repeated(function(cm) {
262
262
  var start = cm.getCursor(), end = cm.clipPos(Pos(start.line));
@@ -353,27 +353,7 @@
353
353
  "Alt-/": "autocomplete",
354
354
  "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto",
355
355
 
356
- "Alt-G": function(cm) {cm.setOption("keyMap", "emacs-Alt-G");},
357
- "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");},
358
- "Ctrl-Q": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-Q");},
359
- "Ctrl-U": addPrefixMap
360
- };
361
-
362
- CodeMirror.keyMap["emacs-Ctrl-X"] = {
363
- "Tab": function(cm) {
364
- cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit"));
365
- },
366
- "Ctrl-X": function(cm) {
367
- cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor"));
368
- },
369
-
370
- "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": repeated("undo"), "K": "close",
371
- "Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); },
372
- auto: "emacs", nofallthrough: true, disableInput: true
373
- };
374
-
375
- CodeMirror.keyMap["emacs-Alt-G"] = {
376
- "G": function(cm) {
356
+ "Alt-G G": function(cm) {
377
357
  var prefix = getPrefix(cm, true);
378
358
  if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1);
379
359
 
@@ -383,13 +363,24 @@
383
363
  cm.setCursor(num - 1);
384
364
  });
385
365
  },
386
- auto: "emacs", nofallthrough: true, disableInput: true
387
- };
388
366
 
389
- CodeMirror.keyMap["emacs-Ctrl-Q"] = {
390
- "Tab": repeated("insertTab"),
391
- auto: "emacs", nofallthrough: true
392
- };
367
+ "Ctrl-X Tab": function(cm) {
368
+ cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit"));
369
+ },
370
+ "Ctrl-X Ctrl-X": function(cm) {
371
+ cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor"));
372
+ },
373
+ "Ctrl-X Ctrl-S": "save",
374
+ "Ctrl-X Ctrl-W": "save",
375
+ "Ctrl-X S": "saveAll",
376
+ "Ctrl-X F": "open",
377
+ "Ctrl-X U": repeated("undo"),
378
+ "Ctrl-X K": "close",
379
+ "Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); },
380
+
381
+ "Ctrl-Q Tab": repeated("insertTab"),
382
+ "Ctrl-U": addPrefixMap
383
+ });
393
384
 
394
385
  var prefixMap = {"Ctrl-G": clearPrefix};
395
386
  function regPrefix(d) {
@@ -386,9 +386,7 @@
386
386
 
387
387
  map["Alt-Q"] = "wrapLines";
388
388
 
389
- var mapK = CodeMirror.keyMap["sublime-Ctrl-K"] = {auto: "sublime", nofallthrough: true};
390
-
391
- map[ctrl + "K"] = function(cm) {cm.setOption("keyMap", "sublime-Ctrl-K");};
389
+ var cK = ctrl + "K ";
392
390
 
393
391
  function modifyWordOrSelection(cm, mod) {
394
392
  cm.operation(function() {
@@ -409,9 +407,9 @@
409
407
  });
410
408
  }
411
409
 
412
- mapK[ctrl + "Backspace"] = "delLineLeft";
410
+ map[cK + ctrl + "Backspace"] = "delLineLeft";
413
411
 
414
- cmds[mapK[ctrl + "K"] = "delLineRight"] = function(cm) {
412
+ cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) {
415
413
  cm.operation(function() {
416
414
  var ranges = cm.listSelections();
417
415
  for (var i = ranges.length - 1; i >= 0; i--)
@@ -420,22 +418,22 @@
420
418
  });
421
419
  };
422
420
 
423
- cmds[mapK[ctrl + "U"] = "upcaseAtCursor"] = function(cm) {
421
+ cmds[map[cK + ctrl + "U"] = "upcaseAtCursor"] = function(cm) {
424
422
  modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); });
425
423
  };
426
- cmds[mapK[ctrl + "L"] = "downcaseAtCursor"] = function(cm) {
424
+ cmds[map[cK + ctrl + "L"] = "downcaseAtCursor"] = function(cm) {
427
425
  modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); });
428
426
  };
429
427
 
430
- cmds[mapK[ctrl + "Space"] = "setSublimeMark"] = function(cm) {
428
+ cmds[map[cK + ctrl + "Space"] = "setSublimeMark"] = function(cm) {
431
429
  if (cm.state.sublimeMark) cm.state.sublimeMark.clear();
432
430
  cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
433
431
  };
434
- cmds[mapK[ctrl + "A"] = "selectToSublimeMark"] = function(cm) {
432
+ cmds[map[cK + ctrl + "A"] = "selectToSublimeMark"] = function(cm) {
435
433
  var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
436
434
  if (found) cm.setSelection(cm.getCursor(), found);
437
435
  };
438
- cmds[mapK[ctrl + "W"] = "deleteToSublimeMark"] = function(cm) {
436
+ cmds[map[cK + ctrl + "W"] = "deleteToSublimeMark"] = function(cm) {
439
437
  var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
440
438
  if (found) {
441
439
  var from = cm.getCursor(), to = found;
@@ -444,7 +442,7 @@
444
442
  cm.replaceRange("", from, to);
445
443
  }
446
444
  };
447
- cmds[mapK[ctrl + "X"] = "swapWithSublimeMark"] = function(cm) {
445
+ cmds[map[cK + ctrl + "X"] = "swapWithSublimeMark"] = function(cm) {
448
446
  var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
449
447
  if (found) {
450
448
  cm.state.sublimeMark.clear();
@@ -452,13 +450,13 @@
452
450
  cm.setCursor(found);
453
451
  }
454
452
  };
455
- cmds[mapK[ctrl + "Y"] = "sublimeYank"] = function(cm) {
453
+ cmds[map[cK + ctrl + "Y"] = "sublimeYank"] = function(cm) {
456
454
  if (cm.state.sublimeKilled != null)
457
455
  cm.replaceSelection(cm.state.sublimeKilled, null, "paste");
458
456
  };
459
457
 
460
- mapK[ctrl + "G"] = "clearBookmarks";
461
- cmds[mapK[ctrl + "C"] = "showInCenter"] = function(cm) {
458
+ map[cK + ctrl + "G"] = "clearBookmarks";
459
+ cmds[map[cK + ctrl + "C"] = "showInCenter"] = function(cm) {
462
460
  var pos = cm.cursorCoords(null, "local");
463
461
  cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2);
464
462
  };
@@ -530,7 +528,7 @@
530
528
 
531
529
  map["Shift-" + ctrl + "["] = "fold";
532
530
  map["Shift-" + ctrl + "]"] = "unfold";
533
- mapK[ctrl + "0"] = mapK[ctrl + "j"] = "unfoldAll";
531
+ map[cK + ctrl + "0"] = map[cK + ctrl + "j"] = "unfoldAll";
534
532
 
535
533
  map[ctrl + "I"] = "findIncremental";
536
534
  map["Shift-" + ctrl + "I"] = "findIncrementalReverse";
@@ -538,4 +536,5 @@
538
536
  map["F3"] = "findNext";
539
537
  map["Shift-F3"] = "findPrev";
540
538
 
539
+ CodeMirror.normalizeKeyMap(map);
541
540
  });
@@ -156,16 +156,22 @@
156
156
  { keys: 'c', type: 'operator', operator: 'change' },
157
157
  { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }},
158
158
  { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }},
159
- { keys: 'g~', type: 'operator', operator: 'swapcase' },
159
+ { keys: 'g~', type: 'operator', operator: 'changeCase' },
160
+ { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true },
161
+ { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true },
160
162
  { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }},
161
163
  { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }},
162
164
  // Operator-Motion dual commands
163
165
  { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }},
164
166
  { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }},
165
- { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }},
166
- { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }},
167
- { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }},
168
- { keys: '~', type: 'operatorMotion', operator: 'swapcase', operatorArgs: { shouldMoveCursor: true }, motion: 'moveByCharacters', motionArgs: { forward: true }},
167
+ { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
168
+ { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'},
169
+ { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
170
+ { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'},
171
+ { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
172
+ { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'},
173
+ { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'},
174
+ { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'},
169
175
  { keys: '<C-w>', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },
170
176
  // Actions
171
177
  { keys: '<C-i>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},
@@ -176,7 +182,8 @@
176
182
  { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
177
183
  { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
178
184
  { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
179
- { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank' }},
185
+ { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' },
186
+ { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' },
180
187
  { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
181
188
  { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
182
189
  { keys: 'v', type: 'action', action: 'toggleVisualMode' },
@@ -192,8 +199,8 @@
192
199
  // Handle Replace-mode as a special case of insert mode.
193
200
  { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }},
194
201
  { keys: 'u', type: 'action', action: 'undo', context: 'normal' },
195
- { keys: 'u', type: 'action', action: 'changeCase', actionArgs: {toLower: true}, context: 'visual', isEdit: true },
196
- { keys: 'U',type: 'action', action: 'changeCase', actionArgs: {toLower: false}, context: 'visual', isEdit: true },
202
+ { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true },
203
+ { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true },
197
204
  { keys: '<C-r>', type: 'action', action: 'redo' },
198
205
  { keys: 'm<character>', type: 'action', action: 'setMark' },
199
206
  { keys: '"<character>', type: 'action', action: 'setRegister' },
@@ -226,70 +233,92 @@
226
233
  var specialKey = {Enter:'CR',Backspace:'BS',Delete:'Del'};
227
234
  var mac = /Mac/.test(navigator.platform);
228
235
  var Vim = function() {
229
- CodeMirror.defineOption('vimMode', false, function(cm, val) {
230
- function lookupKey(e) {
231
- var keyCode = e.keyCode;
232
- if (modifierCodes.indexOf(keyCode) != -1) { return; }
233
- var hasModifier = e.ctrlKey || e.metaKey;
234
- var key = CodeMirror.keyNames[keyCode];
235
- key = specialKey[key] || key;
236
- var name = '';
237
- if (e.ctrlKey) { name += 'C-'; }
238
- if (e.altKey) { name += 'A-'; }
239
- if (mac && e.metaKey || (!hasModifier && e.shiftKey) && key.length < 2) {
240
- // Shift key bindings can only specified for special characters.
241
- return;
242
- } else if (e.shiftKey && !/^[A-Za-z]$/.test(key)) {
243
- name += 'S-';
244
- }
245
- if (key.length == 1) { key = key.toLowerCase(); }
246
- name += key;
247
- if (name.length > 1) { name = '<' + name + '>'; }
248
- return name;
249
- }
250
- // Keys with modifiers are handled using keydown due to limitations of
251
- // keypress event.
252
- function handleKeyDown(cm, e) {
253
- var name = lookupKey(e);
254
- if (!name) { return; }
255
-
256
- CodeMirror.signal(cm, 'vim-keypress', name);
257
- if (CodeMirror.Vim.handleKey(cm, name, 'user')) {
258
- CodeMirror.e_stop(e);
259
- }
260
- }
261
- // Keys without modifiers are handled using keypress to work best with
262
- // non-standard keyboard layouts.
263
- function handleKeyPress(cm, e) {
264
- var code = e.charCode || e.keyCode;
265
- if (e.ctrlKey || e.metaKey || e.altKey ||
266
- e.shiftKey && code < 32) { return; }
267
- var name = String.fromCharCode(code);
268
-
269
- CodeMirror.signal(cm, 'vim-keypress', name);
270
- if (CodeMirror.Vim.handleKey(cm, name, 'user')) {
271
- CodeMirror.e_stop(e);
272
- }
273
- }
274
- if (val) {
275
- cm.setOption('keyMap', 'vim');
276
- cm.setOption('disableInput', true);
277
- cm.setOption('showCursorWhenSelecting', false);
278
- CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
279
- cm.on('cursorActivity', onCursorActivity);
280
- maybeInitVimState(cm);
281
- CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
282
- cm.on('keypress', handleKeyPress);
283
- cm.on('keydown', handleKeyDown);
284
- } else if (cm.state.vim) {
285
- cm.setOption('keyMap', 'default');
286
- cm.setOption('disableInput', false);
287
- cm.off('cursorActivity', onCursorActivity);
288
- CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
289
- cm.state.vim = null;
290
- cm.off('keypress', handleKeyPress);
291
- cm.off('keydown', handleKeyDown);
236
+ function lookupKey(e) {
237
+ var keyCode = e.keyCode;
238
+ if (modifierCodes.indexOf(keyCode) != -1) { return; }
239
+ var hasModifier = e.ctrlKey || e.metaKey;
240
+ var key = CodeMirror.keyNames[keyCode];
241
+ key = specialKey[key] || key;
242
+ var name = '';
243
+ if (e.ctrlKey) { name += 'C-'; }
244
+ if (e.altKey) { name += 'A-'; }
245
+ if (mac && e.metaKey || (!hasModifier && e.shiftKey) && key.length < 2) {
246
+ // Shift key bindings can only specified for special characters.
247
+ return;
248
+ } else if (e.shiftKey && !/^[A-Za-z]$/.test(key)) {
249
+ name += 'S-';
250
+ }
251
+ if (key.length == 1) { key = key.toLowerCase(); }
252
+ name += key;
253
+ if (name.length > 1) { name = '<' + name + '>'; }
254
+ return name;
255
+ }
256
+ // Keys with modifiers are handled using keydown due to limitations of
257
+ // keypress event.
258
+ function handleKeyDown(cm, e) {
259
+ var name = lookupKey(e);
260
+ if (!name) { return; }
261
+
262
+ CodeMirror.signal(cm, 'vim-keypress', name);
263
+ if (CodeMirror.Vim.handleKey(cm, name, 'user')) {
264
+ CodeMirror.e_stop(e);
265
+ }
266
+ }
267
+ // Keys without modifiers are handled using keypress to work best with
268
+ // non-standard keyboard layouts.
269
+ function handleKeyPress(cm, e) {
270
+ var code = e.charCode || e.keyCode;
271
+ if (e.ctrlKey || e.metaKey || e.altKey ||
272
+ e.shiftKey && code < 32) { return; }
273
+ var name = String.fromCharCode(code);
274
+
275
+ CodeMirror.signal(cm, 'vim-keypress', name);
276
+ if (CodeMirror.Vim.handleKey(cm, name, 'user')) {
277
+ CodeMirror.e_stop(e);
292
278
  }
279
+ }
280
+
281
+ function enterVimMode(cm) {
282
+ cm.setOption('disableInput', true);
283
+ cm.setOption('showCursorWhenSelecting', false);
284
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
285
+ cm.on('cursorActivity', onCursorActivity);
286
+ maybeInitVimState(cm);
287
+ CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
288
+ cm.on('keypress', handleKeyPress);
289
+ cm.on('keydown', handleKeyDown);
290
+ }
291
+
292
+ function leaveVimMode(cm) {
293
+ cm.setOption('disableInput', false);
294
+ cm.off('cursorActivity', onCursorActivity);
295
+ CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
296
+ cm.state.vim = null;
297
+ cm.off('keypress', handleKeyPress);
298
+ cm.off('keydown', handleKeyDown);
299
+ }
300
+
301
+ function detachVimMap(cm, next) {
302
+ if (this == CodeMirror.keyMap.vim)
303
+ CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");
304
+
305
+ if (!next || next.attach != attachVimMap)
306
+ leaveVimMode(cm, false);
307
+ }
308
+ function attachVimMap(cm, prev) {
309
+ if (this == CodeMirror.keyMap.vim)
310
+ CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");
311
+
312
+ if (!prev || prev.attach != attachVimMap)
313
+ enterVimMode(cm);
314
+ }
315
+
316
+ // Deprecated, simply setting the keymap works again.
317
+ CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {
318
+ if (val && cm.getOption("keyMap") != "vim")
319
+ cm.setOption("keyMap", "vim");
320
+ else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap")))
321
+ cm.setOption("keyMap", "default");
293
322
  });
294
323
  function getOnPasteFn(cm) {
295
324
  var vim = cm.state.vim;
@@ -533,8 +562,8 @@
533
562
  visualBlock: false,
534
563
  lastSelection: null,
535
564
  lastPastedText: null,
536
- // Used by two-character ESC keymap routines. Should not be changed from false here.
537
- awaitingEscapeSecondCharacter: false
565
+ sel: {
566
+ }
538
567
  };
539
568
  }
540
569
  return cm.state.vim;
@@ -674,7 +703,7 @@
674
703
  } else {
675
704
  commandDispatcher.processCommand(cm, vim, command);
676
705
  }
677
- return !keysAreChars;
706
+ return true;
678
707
  }
679
708
 
680
709
  function handleKeyNonInsertMode() {
@@ -705,8 +734,18 @@
705
734
  return true;
706
735
  }
707
736
 
708
- if (vim.insertMode) { return handleKeyInsertMode(); }
709
- else { return handleKeyNonInsertMode(); }
737
+ return cm.operation(function() {
738
+ cm.curOp.isVimOp = true;
739
+ try {
740
+ if (vim.insertMode) { return handleKeyInsertMode(); }
741
+ else { return handleKeyNonInsertMode(); }
742
+ } catch (e) {
743
+ // clear VIM state in case it's in a bad state.
744
+ cm.state.vim = undefined;
745
+ maybeInitVimState(cm);
746
+ throw e;
747
+ }
748
+ });
710
749
  },
711
750
  handleEx: function(cm, input) {
712
751
  exCommandDispatcher.processCommand(cm, input);
@@ -960,10 +999,12 @@
960
999
  break;
961
1000
  case 'search':
962
1001
  this.processSearch(cm, vim, command);
1002
+ clearInputState(cm);
963
1003
  break;
964
1004
  case 'ex':
965
1005
  case 'keyToEx':
966
1006
  this.processEx(cm, vim, command);
1007
+ clearInputState(cm);
967
1008
  break;
968
1009
  default:
969
1010
  break;
@@ -1205,13 +1246,13 @@
1205
1246
  var operator = inputState.operator;
1206
1247
  var operatorArgs = inputState.operatorArgs || {};
1207
1248
  var registerName = inputState.registerName;
1208
- var selectionEnd = copyCursor(cm.getCursor('head'));
1209
- var selectionStart = copyCursor(cm.getCursor('anchor'));
1210
- // The difference between cur and selection cursors are that cur is
1211
- // being operated on and ignores that there is a selection.
1212
- var curStart = copyCursor(selectionEnd);
1213
- var curOriginal = copyCursor(curStart);
1214
- var curEnd;
1249
+ var sel = vim.sel;
1250
+ // TODO: Make sure cm and vim selections are identical outside visual mode.
1251
+ var origHead = copyCursor(vim.visualMode ? sel.head: cm.getCursor('head'));
1252
+ var origAnchor = copyCursor(vim.visualMode ? sel.anchor : cm.getCursor('anchor'));
1253
+ var oldHead = copyCursor(origHead);
1254
+ var oldAnchor = copyCursor(origAnchor);
1255
+ var newHead, newAnchor;
1215
1256
  var repeat;
1216
1257
  if (operator) {
1217
1258
  this.recordLastEdit(vim, inputState);
@@ -1238,7 +1279,7 @@
1238
1279
  motionArgs.repeat = repeat;
1239
1280
  clearInputState(cm);
1240
1281
  if (motion) {
1241
- var motionResult = motions[motion](cm, motionArgs, vim);
1282
+ var motionResult = motions[motion](cm, origHead, motionArgs, vim);
1242
1283
  vim.lastMotion = motions[motion];
1243
1284
  if (!motionResult) {
1244
1285
  return;
@@ -1251,159 +1292,139 @@
1251
1292
  recordJumpPosition(cm, cachedCursor, motionResult);
1252
1293
  delete jumpList.cachedCursor;
1253
1294
  } else {
1254
- recordJumpPosition(cm, curOriginal, motionResult);
1295
+ recordJumpPosition(cm, origHead, motionResult);
1255
1296
  }
1256
1297
  }
1257
1298
  if (motionResult instanceof Array) {
1258
- curStart = motionResult[0];
1259
- curEnd = motionResult[1];
1299
+ newAnchor = motionResult[0];
1300
+ newHead = motionResult[1];
1260
1301
  } else {
1261
- curEnd = motionResult;
1302
+ newHead = motionResult;
1262
1303
  }
1263
1304
  // TODO: Handle null returns from motion commands better.
1264
- if (!curEnd) {
1265
- curEnd = Pos(curStart.line, curStart.ch);
1305
+ if (!newHead) {
1306
+ newHead = copyCursor(origHead);
1266
1307
  }
1267
1308
  if (vim.visualMode) {
1268
- // Check if the selection crossed over itself. Will need to shift
1269
- // the start point if that happened.
1270
- // offset is set to -1 or 1 to shift the curEnd
1271
- // left or right
1272
- var offset = 0;
1273
- if (cursorIsBefore(selectionStart, selectionEnd) &&
1274
- (cursorEqual(selectionStart, curEnd) ||
1275
- cursorIsBefore(curEnd, selectionStart))) {
1276
- // The end of the selection has moved from after the start to
1277
- // before the start. We will shift the start right by 1.
1278
- selectionStart.ch += 1;
1279
- offset = -1;
1280
- } else if (cursorIsBefore(selectionEnd, selectionStart) &&
1281
- (cursorEqual(selectionStart, curEnd) ||
1282
- cursorIsBefore(selectionStart, curEnd))) {
1283
- // The opposite happened. We will shift the start left by 1.
1284
- selectionStart.ch -= 1;
1285
- offset = 1;
1286
- }
1287
- // in case of visual Block selectionStart and curEnd
1288
- // may not be on the same line,
1289
- // Also, In case of v_o this should not happen.
1290
- if (!vim.visualBlock && !(motionResult instanceof Array)) {
1291
- curEnd.ch += offset;
1292
- }
1293
- if (vim.lastHPos != Infinity) {
1294
- vim.lastHPos = curEnd.ch;
1295
- }
1296
- selectionEnd = curEnd;
1297
- selectionStart = (motionResult instanceof Array) ? curStart : selectionStart;
1298
- if (vim.visualLine) {
1299
- if (cursorIsBefore(selectionStart, selectionEnd)) {
1300
- selectionStart.ch = 0;
1301
-
1302
- var lastLine = cm.lastLine();
1303
- if (selectionEnd.line > lastLine) {
1304
- selectionEnd.line = lastLine;
1305
- }
1306
- selectionEnd.ch = lineLength(cm, selectionEnd.line);
1307
- } else {
1308
- selectionEnd.ch = 0;
1309
- selectionStart.ch = lineLength(cm, selectionStart.line);
1310
- }
1311
- } else if (vim.visualBlock) {
1312
- // Select a block and
1313
- // return the diagonally opposite end.
1314
- selectionStart = selectBlock(cm, selectionEnd);
1315
- }
1316
- if (!vim.visualBlock) {
1317
- cm.setSelection(selectionStart, selectionEnd);
1309
+ newHead = clipCursorToContent(cm, newHead, vim.visualBlock);
1310
+ if (newAnchor) {
1311
+ newAnchor = clipCursorToContent(cm, newAnchor, true);
1318
1312
  }
1313
+ newAnchor = newAnchor || oldAnchor;
1314
+ sel.anchor = newAnchor;
1315
+ sel.head = newHead;
1316
+ updateCmSelection(cm);
1319
1317
  updateMark(cm, vim, '<',
1320
- cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
1321
- : selectionEnd);
1318
+ cursorIsBefore(newAnchor, newHead) ? newAnchor
1319
+ : newHead);
1322
1320
  updateMark(cm, vim, '>',
1323
- cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd
1324
- : selectionStart);
1321
+ cursorIsBefore(newAnchor, newHead) ? newHead
1322
+ : newAnchor);
1325
1323
  } else if (!operator) {
1326
- curEnd = clipCursorToContent(cm, curEnd);
1327
- cm.setCursor(curEnd.line, curEnd.ch);
1324
+ newHead = clipCursorToContent(cm, newHead);
1325
+ cm.setCursor(newHead.line, newHead.ch);
1328
1326
  }
1329
1327
  }
1330
-
1331
1328
  if (operator) {
1332
- var inverted = false;
1333
- vim.lastMotion = null;
1334
- var lastSelection = vim.lastSelection;
1335
- operatorArgs.repeat = repeat; // Indent in visual mode needs this.
1336
- if (vim.visualMode) {
1337
- curStart = selectionStart;
1338
- curEnd = selectionEnd;
1339
- motionArgs.inclusive = true;
1340
- operatorArgs.shouldMoveCursor = false;
1341
- }
1342
- // Swap start and end if motion was backward.
1343
- if (curEnd && cursorIsBefore(curEnd, curStart)) {
1344
- var tmp = curStart;
1345
- curStart = curEnd;
1346
- curEnd = tmp;
1347
- inverted = true;
1348
- } else if (!curEnd) {
1349
- curEnd = copyCursor(curStart);
1350
- }
1351
- if (motionArgs.inclusive && !vim.visualMode) {
1352
- // Move the selection end one to the right to include the last
1353
- // character.
1354
- curEnd.ch++;
1355
- }
1356
- if (operatorArgs.selOffset) {
1329
+ if (operatorArgs.lastSel) {
1357
1330
  // Replaying a visual mode operation
1358
- curEnd.line = curStart.line + operatorArgs.selOffset.line;
1359
- if (operatorArgs.selOffset.line) {curEnd.ch = operatorArgs.selOffset.ch; }
1360
- else { curEnd.ch = curStart.ch + operatorArgs.selOffset.ch; }
1361
- // In case of blockwise visual
1362
- if (lastSelection && lastSelection.visualBlock) {
1363
- var block = lastSelection.visualBlock;
1364
- var width = block.width;
1365
- var height = block.height;
1366
- curEnd = Pos(curStart.line + height, curStart.ch + width);
1367
- // selectBlock creates a 'proper' rectangular block.
1368
- // We do not want that in all cases, so we manually set selections.
1369
- var selections = [];
1370
- for (var i = curStart.line; i < curEnd.line; i++) {
1371
- var anchor = Pos(i, curStart.ch);
1372
- var head = Pos(i, curEnd.ch);
1373
- var range = {anchor: anchor, head: head};
1374
- selections.push(range);
1375
- }
1376
- cm.setSelections(selections);
1377
- var blockSelected = true;
1331
+ newAnchor = oldAnchor;
1332
+ var lastSel = operatorArgs.lastSel;
1333
+ var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line);
1334
+ var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch);
1335
+ if (lastSel.visualLine) {
1336
+ // Linewise Visual mode: The same number of lines.
1337
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
1338
+ } else if (lastSel.visualBlock) {
1339
+ // Blockwise Visual mode: The same number of lines and columns.
1340
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset);
1341
+ } else if (lastSel.head.line == lastSel.anchor.line) {
1342
+ // Normal Visual mode within one line: The same number of characters.
1343
+ newHead = Pos(oldAnchor.line, oldAnchor.ch + chOffset);
1344
+ } else {
1345
+ // Normal Visual mode with several lines: The same number of lines, in the
1346
+ // last line the same number of characters as in the last line the last time.
1347
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
1378
1348
  }
1349
+ vim.visualMode = true;
1350
+ vim.visualLine = lastSel.visualLine;
1351
+ vim.visualBlock = lastSel.visualBlock;
1352
+ sel = vim.sel = {
1353
+ anchor: newAnchor,
1354
+ head: newHead
1355
+ };
1356
+ updateCmSelection(cm);
1379
1357
  } else if (vim.visualMode) {
1380
- var selOffset = Pos();
1381
- selOffset.line = curEnd.line - curStart.line;
1382
- if (selOffset.line) { selOffset.ch = curEnd.ch; }
1383
- else { selOffset.ch = curEnd.ch - curStart.ch; }
1384
- operatorArgs.selOffset = selOffset;
1358
+ operatorArgs.lastSel = {
1359
+ anchor: copyCursor(sel.anchor),
1360
+ head: copyCursor(sel.head),
1361
+ visualBlock: vim.visualBlock,
1362
+ visualLine: vim.visualLine
1363
+ };
1385
1364
  }
1386
- var linewise = motionArgs.linewise ||
1387
- (vim.visualMode && vim.visualLine) ||
1388
- operatorArgs.linewise;
1389
- if (linewise) {
1390
- // Expand selection to entire line.
1391
- expandSelectionToLine(cm, curStart, curEnd);
1392
- } else if (motionArgs.forward) {
1393
- // Clip to trailing newlines only if the motion goes forward.
1394
- clipToLine(cm, curStart, curEnd);
1365
+ var curStart, curEnd, linewise, mode;
1366
+ var cmSel;
1367
+ if (vim.visualMode) {
1368
+ // Init visual op
1369
+ curStart = cursorMin(sel.head, sel.anchor);
1370
+ curEnd = cursorMax(sel.head, sel.anchor);
1371
+ linewise = vim.visualLine || operatorArgs.linewise;
1372
+ mode = vim.visualBlock ? 'block' :
1373
+ linewise ? 'line' :
1374
+ 'char';
1375
+ cmSel = makeCmSelection(cm, {
1376
+ anchor: curStart,
1377
+ head: curEnd
1378
+ }, mode);
1379
+ if (linewise) {
1380
+ var ranges = cmSel.ranges;
1381
+ if (mode == 'block') {
1382
+ // Linewise operators in visual block mode extend to end of line
1383
+ for (var i = 0; i < ranges.length; i++) {
1384
+ ranges[i].head.ch = lineLength(cm, ranges[i].head.line);
1385
+ }
1386
+ } else if (mode == 'line') {
1387
+ ranges[0].head = Pos(ranges[0].head.line + 1, 0);
1388
+ }
1389
+ }
1390
+ } else {
1391
+ // Init motion op
1392
+ curStart = copyCursor(newAnchor || oldAnchor);
1393
+ curEnd = copyCursor(newHead || oldHead);
1394
+ if (cursorIsBefore(curEnd, curStart)) {
1395
+ var tmp = curStart;
1396
+ curStart = curEnd;
1397
+ curEnd = tmp;
1398
+ }
1399
+ linewise = motionArgs.linewise || operatorArgs.linewise;
1400
+ if (linewise) {
1401
+ // Expand selection to entire line.
1402
+ expandSelectionToLine(cm, curStart, curEnd);
1403
+ } else if (motionArgs.forward) {
1404
+ // Clip to trailing newlines only if the motion goes forward.
1405
+ clipToLine(cm, curStart, curEnd);
1406
+ }
1407
+ mode = 'char';
1408
+ var exclusive = !motionArgs.inclusive || linewise;
1409
+ cmSel = makeCmSelection(cm, {
1410
+ anchor: curStart,
1411
+ head: curEnd
1412
+ }, mode, exclusive);
1395
1413
  }
1414
+ cm.setSelections(cmSel.ranges, cmSel.primary);
1415
+ vim.lastMotion = null;
1416
+ operatorArgs.repeat = repeat; // For indent in visual mode.
1396
1417
  operatorArgs.registerName = registerName;
1397
1418
  // Keep track of linewise as it affects how paste and change behave.
1398
1419
  operatorArgs.linewise = linewise;
1399
- if (!vim.visualBlock && !blockSelected) {
1400
- cm.setSelection(curStart, curEnd);
1401
- }
1402
- operators[operator](cm, operatorArgs, vim, curStart,
1403
- curEnd, curOriginal);
1420
+ var operatorMoveTo = operators[operator](
1421
+ cm, operatorArgs, cmSel.ranges, oldAnchor, newHead);
1404
1422
  if (vim.visualMode) {
1405
1423
  exitVisualMode(cm);
1406
1424
  }
1425
+ if (operatorMoveTo) {
1426
+ cm.setCursor(operatorMoveTo);
1427
+ }
1407
1428
  }
1408
1429
  },
1409
1430
  recordLastEdit: function(vim, inputState, actionCommand) {
@@ -1422,7 +1443,7 @@
1422
1443
  */
1423
1444
  // All of the functions below return Cursor objects.
1424
1445
  var motions = {
1425
- moveToTopLine: function(cm, motionArgs) {
1446
+ moveToTopLine: function(cm, _head, motionArgs) {
1426
1447
  var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
1427
1448
  return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
1428
1449
  },
@@ -1431,17 +1452,17 @@
1431
1452
  var line = Math.floor((range.top + range.bottom) * 0.5);
1432
1453
  return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
1433
1454
  },
1434
- moveToBottomLine: function(cm, motionArgs) {
1455
+ moveToBottomLine: function(cm, _head, motionArgs) {
1435
1456
  var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
1436
1457
  return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
1437
1458
  },
1438
- expandToLine: function(cm, motionArgs) {
1459
+ expandToLine: function(_cm, head, motionArgs) {
1439
1460
  // Expands forward to end of line, and then to next line if repeat is
1440
1461
  // >1. Does not handle backward motion!
1441
- var cur = cm.getCursor();
1462
+ var cur = head;
1442
1463
  return Pos(cur.line + motionArgs.repeat - 1, Infinity);
1443
1464
  },
1444
- findNext: function(cm, motionArgs) {
1465
+ findNext: function(cm, _head, motionArgs) {
1445
1466
  var state = getSearchState(cm);
1446
1467
  var query = state.getQuery();
1447
1468
  if (!query) {
@@ -1453,7 +1474,7 @@
1453
1474
  highlightSearchMatches(cm, query);
1454
1475
  return findNext(cm, prev/** prev */, query, motionArgs.repeat);
1455
1476
  },
1456
- goToMark: function(cm, motionArgs, vim) {
1477
+ goToMark: function(cm, _head, motionArgs, vim) {
1457
1478
  var mark = vim.marks[motionArgs.selectedCharacter];
1458
1479
  if (mark) {
1459
1480
  var pos = mark.find();
@@ -1461,22 +1482,19 @@
1461
1482
  }
1462
1483
  return null;
1463
1484
  },
1464
- moveToOtherHighlightedEnd: function(cm, motionArgs, vim) {
1465
- var ranges = cm.listSelections();
1466
- var curEnd = cm.getCursor('head');
1467
- var curStart = ranges[0].anchor;
1468
- var curIndex = cursorEqual(ranges[0].head, curEnd) ? ranges.length-1 : 0;
1469
- if (motionArgs.sameLine && vim.visualBlock) {
1470
- curStart = Pos(curEnd.line, ranges[curIndex].anchor.ch);
1471
- curEnd = Pos(ranges[curIndex].head.line, curEnd.ch);
1485
+ moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) {
1486
+ if (vim.visualBlock && motionArgs.sameLine) {
1487
+ var sel = vim.sel;
1488
+ return [
1489
+ clipCursorToContent(cm, Pos(sel.anchor.line, sel.head.ch)),
1490
+ clipCursorToContent(cm, Pos(sel.head.line, sel.anchor.ch))
1491
+ ];
1472
1492
  } else {
1473
- curStart = ranges[curIndex].anchor;
1493
+ return ([vim.sel.head, vim.sel.anchor]);
1474
1494
  }
1475
- cm.setCursor(curEnd);
1476
- return ([curEnd, curStart]);
1477
1495
  },
1478
- jumpToMark: function(cm, motionArgs, vim) {
1479
- var best = cm.getCursor();
1496
+ jumpToMark: function(cm, head, motionArgs, vim) {
1497
+ var best = head;
1480
1498
  for (var i = 0; i < motionArgs.repeat; i++) {
1481
1499
  var cursor = best;
1482
1500
  for (var key in vim.marks) {
@@ -1513,14 +1531,14 @@
1513
1531
  }
1514
1532
  return best;
1515
1533
  },
1516
- moveByCharacters: function(cm, motionArgs) {
1517
- var cur = cm.getCursor();
1534
+ moveByCharacters: function(_cm, head, motionArgs) {
1535
+ var cur = head;
1518
1536
  var repeat = motionArgs.repeat;
1519
1537
  var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
1520
1538
  return Pos(cur.line, ch);
1521
1539
  },
1522
- moveByLines: function(cm, motionArgs, vim) {
1523
- var cur = cm.getCursor();
1540
+ moveByLines: function(cm, head, motionArgs, vim) {
1541
+ var cur = head;
1524
1542
  var endCh = cur.ch;
1525
1543
  // Depending what our last motion was, we may want to do different
1526
1544
  // things. If our last motion was moving vertically, we want to
@@ -1555,8 +1573,8 @@
1555
1573
  vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left;
1556
1574
  return Pos(line, endCh);
1557
1575
  },
1558
- moveByDisplayLines: function(cm, motionArgs, vim) {
1559
- var cur = cm.getCursor();
1576
+ moveByDisplayLines: function(cm, head, motionArgs, vim) {
1577
+ var cur = head;
1560
1578
  switch (vim.lastMotion) {
1561
1579
  case this.moveByDisplayLines:
1562
1580
  case this.moveByScroll:
@@ -1583,16 +1601,16 @@
1583
1601
  vim.lastHPos = res.ch;
1584
1602
  return res;
1585
1603
  },
1586
- moveByPage: function(cm, motionArgs) {
1604
+ moveByPage: function(cm, head, motionArgs) {
1587
1605
  // CodeMirror only exposes functions that move the cursor page down, so
1588
1606
  // doing this bad hack to move the cursor and move it back. evalInput
1589
1607
  // will move the cursor to where it should be in the end.
1590
- var curStart = cm.getCursor();
1608
+ var curStart = head;
1591
1609
  var repeat = motionArgs.repeat;
1592
1610
  return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page');
1593
1611
  },
1594
- moveByParagraph: function(cm, motionArgs) {
1595
- var line = cm.getCursor().line;
1612
+ moveByParagraph: function(cm, head, motionArgs) {
1613
+ var line = head.line;
1596
1614
  var repeat = motionArgs.repeat;
1597
1615
  var inc = motionArgs.forward ? 1 : -1;
1598
1616
  for (var i = 0; i < repeat; i++) {
@@ -1607,16 +1625,16 @@
1607
1625
  }
1608
1626
  return Pos(line, 0);
1609
1627
  },
1610
- moveByScroll: function(cm, motionArgs, vim) {
1628
+ moveByScroll: function(cm, head, motionArgs, vim) {
1611
1629
  var scrollbox = cm.getScrollInfo();
1612
1630
  var curEnd = null;
1613
1631
  var repeat = motionArgs.repeat;
1614
1632
  if (!repeat) {
1615
1633
  repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
1616
1634
  }
1617
- var orig = cm.charCoords(cm.getCursor(), 'local');
1635
+ var orig = cm.charCoords(head, 'local');
1618
1636
  motionArgs.repeat = repeat;
1619
- var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim);
1637
+ var curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim);
1620
1638
  if (!curEnd) {
1621
1639
  return null;
1622
1640
  }
@@ -1624,11 +1642,11 @@
1624
1642
  cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
1625
1643
  return curEnd;
1626
1644
  },
1627
- moveByWords: function(cm, motionArgs) {
1628
- return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward,
1645
+ moveByWords: function(cm, head, motionArgs) {
1646
+ return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward,
1629
1647
  !!motionArgs.wordEnd, !!motionArgs.bigWord);
1630
1648
  },
1631
- moveTillCharacter: function(cm, motionArgs) {
1649
+ moveTillCharacter: function(cm, _head, motionArgs) {
1632
1650
  var repeat = motionArgs.repeat;
1633
1651
  var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
1634
1652
  motionArgs.selectedCharacter);
@@ -1638,26 +1656,26 @@
1638
1656
  curEnd.ch += increment;
1639
1657
  return curEnd;
1640
1658
  },
1641
- moveToCharacter: function(cm, motionArgs) {
1659
+ moveToCharacter: function(cm, head, motionArgs) {
1642
1660
  var repeat = motionArgs.repeat;
1643
1661
  recordLastCharacterSearch(0, motionArgs);
1644
1662
  return moveToCharacter(cm, repeat, motionArgs.forward,
1645
- motionArgs.selectedCharacter) || cm.getCursor();
1663
+ motionArgs.selectedCharacter) || head;
1646
1664
  },
1647
- moveToSymbol: function(cm, motionArgs) {
1665
+ moveToSymbol: function(cm, head, motionArgs) {
1648
1666
  var repeat = motionArgs.repeat;
1649
1667
  return findSymbol(cm, repeat, motionArgs.forward,
1650
- motionArgs.selectedCharacter) || cm.getCursor();
1668
+ motionArgs.selectedCharacter) || head;
1651
1669
  },
1652
- moveToColumn: function(cm, motionArgs, vim) {
1670
+ moveToColumn: function(cm, head, motionArgs, vim) {
1653
1671
  var repeat = motionArgs.repeat;
1654
1672
  // repeat is equivalent to which column we want to move to!
1655
1673
  vim.lastHPos = repeat - 1;
1656
- vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left;
1674
+ vim.lastHSPos = cm.charCoords(head,'div').left;
1657
1675
  return moveToColumn(cm, repeat);
1658
1676
  },
1659
- moveToEol: function(cm, motionArgs, vim) {
1660
- var cur = cm.getCursor();
1677
+ moveToEol: function(cm, head, motionArgs, vim) {
1678
+ var cur = head;
1661
1679
  vim.lastHPos = Infinity;
1662
1680
  var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity);
1663
1681
  var end=cm.clipPos(retval);
@@ -1665,15 +1683,15 @@
1665
1683
  vim.lastHSPos = cm.charCoords(end,'div').left;
1666
1684
  return retval;
1667
1685
  },
1668
- moveToFirstNonWhiteSpaceCharacter: function(cm) {
1686
+ moveToFirstNonWhiteSpaceCharacter: function(cm, head) {
1669
1687
  // Go to the start of the line where the text begins, or the end for
1670
1688
  // whitespace-only lines
1671
- var cursor = cm.getCursor();
1689
+ var cursor = head;
1672
1690
  return Pos(cursor.line,
1673
1691
  findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));
1674
1692
  },
1675
- moveToMatchedSymbol: function(cm) {
1676
- var cursor = cm.getCursor();
1693
+ moveToMatchedSymbol: function(cm, head) {
1694
+ var cursor = head;
1677
1695
  var line = cursor.line;
1678
1696
  var ch = cursor.ch;
1679
1697
  var lineText = cm.getLine(line);
@@ -1694,11 +1712,10 @@
1694
1712
  return cursor;
1695
1713
  }
1696
1714
  },
1697
- moveToStartOfLine: function(cm) {
1698
- var cursor = cm.getCursor();
1699
- return Pos(cursor.line, 0);
1715
+ moveToStartOfLine: function(_cm, head) {
1716
+ return Pos(head.line, 0);
1700
1717
  },
1701
- moveToLineOrEdgeOfDocument: function(cm, motionArgs) {
1718
+ moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) {
1702
1719
  var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
1703
1720
  if (motionArgs.repeatIsExplicit) {
1704
1721
  lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
@@ -1706,7 +1723,7 @@
1706
1723
  return Pos(lineNum,
1707
1724
  findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)));
1708
1725
  },
1709
- textObjectManipulation: function(cm, motionArgs) {
1726
+ textObjectManipulation: function(cm, head, motionArgs) {
1710
1727
  // TODO: lots of possible exceptions that can be thrown here. Try da(
1711
1728
  // outside of a () block.
1712
1729
 
@@ -1735,9 +1752,9 @@
1735
1752
 
1736
1753
  var tmp;
1737
1754
  if (mirroredPairs[character]) {
1738
- tmp = selectCompanionObject(cm, character, inclusive);
1755
+ tmp = selectCompanionObject(cm, head, character, inclusive);
1739
1756
  } else if (selfPaired[character]) {
1740
- tmp = findBeginningAndEnd(cm, character, inclusive);
1757
+ tmp = findBeginningAndEnd(cm, head, character, inclusive);
1741
1758
  } else if (character === 'W') {
1742
1759
  tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
1743
1760
  true /** bigWord */);
@@ -1756,7 +1773,7 @@
1756
1773
  }
1757
1774
  },
1758
1775
 
1759
- repeatLastCharacterSearch: function(cm, motionArgs) {
1776
+ repeatLastCharacterSearch: function(cm, head, motionArgs) {
1760
1777
  var lastSearch = vimGlobalState.lastChararacterSearch;
1761
1778
  var repeat = motionArgs.repeat;
1762
1779
  var forward = motionArgs.forward === lastSearch.forward;
@@ -1766,138 +1783,107 @@
1766
1783
  var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
1767
1784
  if (!curEnd) {
1768
1785
  cm.moveH(increment, 'char');
1769
- return cm.getCursor();
1786
+ return head;
1770
1787
  }
1771
1788
  curEnd.ch += increment;
1772
1789
  return curEnd;
1773
1790
  }
1774
1791
  };
1775
1792
 
1793
+ function fillArray(val, times) {
1794
+ var arr = [];
1795
+ for (var i = 0; i < times; i++) {
1796
+ arr.push(val);
1797
+ }
1798
+ return arr;
1799
+ }
1800
+ /**
1801
+ * An operator acts on a text selection. It receives the list of selections
1802
+ * as input. The corresponding CodeMirror selection is guaranteed to
1803
+ * match the input selection.
1804
+ */
1776
1805
  var operators = {
1777
- change: function(cm, operatorArgs, vim) {
1778
- var selections = cm.listSelections();
1779
- var start = selections[0], end = selections[selections.length-1];
1780
- var curStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
1781
- var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
1782
- var text = cm.getSelection();
1783
- var visualBlock = vim.visualBlock;
1784
- if (vim.lastSelection && !vim.visualMode) {
1785
- visualBlock = vim.lastSelection.visualBlock ? true : visualBlock;
1786
- }
1787
- var lastInsertModeChanges = vimGlobalState.macroModeState.lastInsertModeChanges;
1788
- lastInsertModeChanges.inVisualBlock = visualBlock;
1789
- var replacement = new Array(selections.length).join('1').split('1');
1790
- // save the selectionEnd mark
1791
- var selectionEnd = vim.marks['>'] ? vim.marks['>'].find() : cm.getCursor('head');
1792
- vimGlobalState.registerController.pushText(
1793
- operatorArgs.registerName, 'change', text,
1794
- operatorArgs.linewise);
1795
- if (operatorArgs.linewise) {
1796
- // 'C' in visual block extends the block till eol for all lines
1797
- if (visualBlock){
1798
- var startLine = curStart.line;
1799
- while (startLine <= curEnd.line) {
1800
- var endCh = lineLength(cm, startLine);
1801
- var head = Pos(startLine, endCh);
1802
- var anchor = Pos(startLine, curStart.ch);
1803
- startLine++;
1804
- cm.replaceRange('', anchor, head);
1805
- }
1806
- } else {
1807
- // Push the next line back down, if there is a next line.
1808
- replacement = '\n';
1809
- if (curEnd.line == curStart.line && curEnd.line == cm.lastLine()) {
1810
- replacement = '';
1811
- }
1812
- cm.replaceRange(replacement, curStart, curEnd);
1813
- cm.indentLine(curStart.line, 'smart');
1814
- // null ch so setCursor moves to end of line.
1815
- curStart.ch = null;
1816
- cm.setCursor(curStart);
1817
- }
1818
- } else {
1819
- // Exclude trailing whitespace if the range is not all whitespace.
1820
- var text = cm.getRange(curStart, curEnd);
1806
+ change: function(cm, args, ranges) {
1807
+ var finalHead, text;
1808
+ var vim = cm.state.vim;
1809
+ vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock = vim.visualBlock;
1810
+ if (!vim.visualMode) {
1811
+ var anchor = ranges[0].anchor,
1812
+ head = ranges[0].head;
1813
+ text = cm.getRange(anchor, head);
1821
1814
  if (!isWhiteSpaceString(text)) {
1815
+ // Exclude trailing whitespace if the range is not all whitespace.
1822
1816
  var match = (/\s+$/).exec(text);
1823
1817
  if (match) {
1824
- curEnd = offsetCursor(curEnd, 0, - match[0].length);
1818
+ head = offsetCursor(head, 0, - match[0].length);
1819
+ text = text.slice(0, - match[0].length);
1825
1820
  }
1826
1821
  }
1827
- if (visualBlock) {
1828
- cm.replaceSelections(replacement);
1829
- } else {
1830
- cm.setCursor(curStart);
1831
- cm.replaceRange('', curStart, curEnd);
1822
+ var wasLastLine = head.line - 1 == cm.lastLine();
1823
+ cm.replaceRange('', anchor, head);
1824
+ if (args.linewise && !wasLastLine) {
1825
+ // Push the next line back down, if there is a next line.
1826
+ CodeMirror.commands.newlineAndIndent(cm);
1827
+ // null ch so setCursor moves to end of line.
1828
+ anchor.ch = null;
1832
1829
  }
1830
+ finalHead = anchor;
1831
+ } else {
1832
+ text = cm.getSelection();
1833
+ var replacement = fillArray('', ranges.length);
1834
+ cm.replaceSelections(replacement);
1835
+ finalHead = cursorMin(ranges[0].head, ranges[0].anchor);
1833
1836
  }
1834
- vim.marks['>'] = cm.setBookmark(selectionEnd);
1835
- actions.enterInsertMode(cm, {}, cm.state.vim);
1837
+ vimGlobalState.registerController.pushText(
1838
+ args.registerName, 'change', text,
1839
+ args.linewise, ranges.length > 1);
1840
+ actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim);
1836
1841
  },
1837
1842
  // delete is a javascript keyword.
1838
- 'delete': function(cm, operatorArgs, vim) {
1839
- var selections = cm.listSelections();
1840
- var start = selections[0], end = selections[selections.length-1];
1841
- var curStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
1842
- var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
1843
- // Save the '>' mark before cm.replaceRange clears it.
1844
- var selectionEnd, selectionStart;
1845
- var blockwise = vim.visualBlock;
1846
- if (vim.visualMode) {
1847
- selectionEnd = vim.marks['>'].find();
1848
- selectionStart = vim.marks['<'].find();
1849
- } else if (vim.lastSelection) {
1850
- selectionEnd = vim.lastSelection.curStartMark.find();
1851
- selectionStart = vim.lastSelection.curEndMark.find();
1852
- blockwise = vim.lastSelection.visualBlock;
1853
- }
1854
- var text = cm.getSelection();
1855
- vimGlobalState.registerController.pushText(
1856
- operatorArgs.registerName, 'delete', text,
1857
- operatorArgs.linewise, blockwise);
1858
- var replacement = new Array(selections.length).join('1').split('1');
1859
- // If the ending line is past the last line, inclusive, instead of
1860
- // including the trailing \n, include the \n before the starting line
1861
- if (operatorArgs.linewise &&
1862
- curEnd.line == cm.lastLine() && curStart.line == curEnd.line) {
1863
- if (curEnd.line == 0) {
1864
- curStart.ch = 0;
1843
+ 'delete': function(cm, args, ranges) {
1844
+ var finalHead, text;
1845
+ var vim = cm.state.vim;
1846
+ if (!vim.visualBlock) {
1847
+ var anchor = ranges[0].anchor,
1848
+ head = ranges[0].head;
1849
+ if (args.linewise &&
1850
+ head.line != cm.firstLine() &&
1851
+ anchor.line == cm.lastLine() &&
1852
+ anchor.line == head.line - 1) {
1853
+ // Special case for dd on last line (and first line).
1854
+ if (anchor.line == cm.firstLine()) {
1855
+ anchor.ch = 0;
1856
+ } else {
1857
+ anchor = Pos(anchor.line - 1, lineLength(cm, anchor.line - 1));
1858
+ }
1865
1859
  }
1866
- else {
1867
- var tmp = copyCursor(curEnd);
1868
- curStart.line--;
1869
- curStart.ch = lineLength(cm, curStart.line);
1870
- curEnd = tmp;
1860
+ text = cm.getRange(anchor, head);
1861
+ cm.replaceRange('', anchor, head);
1862
+ finalHead = anchor;
1863
+ if (args.linewise) {
1864
+ finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor);
1871
1865
  }
1872
- cm.replaceRange('', curStart, curEnd);
1873
1866
  } else {
1867
+ text = cm.getSelection();
1868
+ var replacement = fillArray('', ranges.length);
1874
1869
  cm.replaceSelections(replacement);
1870
+ finalHead = ranges[0].anchor;
1875
1871
  }
1876
- // restore the saved bookmark
1877
- if (selectionEnd) {
1878
- var curStartMark = cm.setBookmark(selectionStart);
1879
- var curEndMark = cm.setBookmark(selectionEnd);
1880
- if (vim.visualMode) {
1881
- vim.marks['<'] = curStartMark;
1882
- vim.marks['>'] = curEndMark;
1883
- } else {
1884
- vim.lastSelection.curStartMark = curStartMark;
1885
- vim.lastSelection.curEndMark = curEndMark;
1886
- }
1887
- }
1888
- if (operatorArgs.linewise) {
1889
- cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1890
- } else {
1891
- cm.setCursor(curStart);
1892
- }
1872
+ vimGlobalState.registerController.pushText(
1873
+ args.registerName, 'delete', text,
1874
+ args.linewise, vim.visualBlock);
1875
+ return finalHead;
1893
1876
  },
1894
- indent: function(cm, operatorArgs, vim, curStart, curEnd) {
1895
- var startLine = curStart.line;
1896
- var endLine = curEnd.line;
1877
+ indent: function(cm, args, ranges) {
1878
+ var vim = cm.state.vim;
1879
+ var startLine = ranges[0].anchor.line;
1880
+ var endLine = vim.visualBlock ?
1881
+ ranges[ranges.length - 1].anchor.line :
1882
+ ranges[0].head.line;
1897
1883
  // In visual mode, n> shifts the selection right n times, instead of
1898
1884
  // shifting n lines right once.
1899
- var repeat = (vim.visualMode) ? operatorArgs.repeat : 1;
1900
- if (operatorArgs.linewise) {
1885
+ var repeat = (vim.visualMode) ? args.repeat : 1;
1886
+ if (args.linewise) {
1901
1887
  // The only way to delete a newline is to delete until the start of
1902
1888
  // the next line, so in linewise mode evalInput will include the next
1903
1889
  // line. We don't want this in indent, so we go back a line.
@@ -1905,39 +1891,52 @@
1905
1891
  }
1906
1892
  for (var i = startLine; i <= endLine; i++) {
1907
1893
  for (var j = 0; j < repeat; j++) {
1908
- cm.indentLine(i, operatorArgs.indentRight);
1894
+ cm.indentLine(i, args.indentRight);
1909
1895
  }
1910
1896
  }
1911
- cm.setCursor(curStart);
1912
- cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1897
+ return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
1913
1898
  },
1914
- swapcase: function(cm, operatorArgs, _vim, _curStart, _curEnd, _curOriginal) {
1899
+ changeCase: function(cm, args, ranges, oldAnchor, newHead) {
1915
1900
  var selections = cm.getSelections();
1916
- var ranges = cm.listSelections();
1917
1901
  var swapped = [];
1902
+ var toLower = args.toLower;
1918
1903
  for (var j = 0; j < selections.length; j++) {
1919
1904
  var toSwap = selections[j];
1920
1905
  var text = '';
1921
- for (var i = 0; i < toSwap.length; i++) {
1922
- var character = toSwap.charAt(i);
1923
- text += isUpperCase(character) ? character.toLowerCase() :
1924
- character.toUpperCase();
1906
+ if (toLower === true) {
1907
+ text = toSwap.toLowerCase();
1908
+ } else if (toLower === false) {
1909
+ text = toSwap.toUpperCase();
1910
+ } else {
1911
+ for (var i = 0; i < toSwap.length; i++) {
1912
+ var character = toSwap.charAt(i);
1913
+ text += isUpperCase(character) ? character.toLowerCase() :
1914
+ character.toUpperCase();
1915
+ }
1925
1916
  }
1926
1917
  swapped.push(text);
1927
1918
  }
1928
1919
  cm.replaceSelections(swapped);
1929
- var curStart = ranges[0].anchor;
1930
- var curEnd = ranges[0].head;
1931
- if (!operatorArgs.shouldMoveCursor) {
1932
- cm.setCursor(cursorIsBefore(curStart, curEnd) ? curStart : curEnd);
1920
+ if (args.shouldMoveCursor){
1921
+ return newHead;
1922
+ } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) {
1923
+ return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor);
1924
+ } else if (args.linewise){
1925
+ return oldAnchor;
1926
+ } else {
1927
+ return cursorMin(ranges[0].anchor, ranges[0].head);
1933
1928
  }
1934
1929
  },
1935
- yank: function(cm, operatorArgs, vim, _curStart, _curEnd, curOriginal) {
1930
+ yank: function(cm, args, ranges, oldAnchor) {
1931
+ var vim = cm.state.vim;
1936
1932
  var text = cm.getSelection();
1933
+ var endPos = vim.visualMode
1934
+ ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor)
1935
+ : oldAnchor;
1937
1936
  vimGlobalState.registerController.pushText(
1938
- operatorArgs.registerName, 'yank',
1939
- text, operatorArgs.linewise, vim.visualBlock);
1940
- cm.setCursor(curOriginal);
1937
+ args.registerName, 'yank',
1938
+ text, args.linewise, vim.visualBlock);
1939
+ return endPos;
1941
1940
  }
1942
1941
  };
1943
1942
 
@@ -2029,41 +2028,40 @@
2029
2028
  vim.insertMode = true;
2030
2029
  vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
2031
2030
  var insertAt = (actionArgs) ? actionArgs.insertAt : null;
2032
- if (vim.visualMode) {
2033
- var selections = getSelectedAreaRange(cm, vim);
2034
- var selectionStart = selections[0];
2035
- var selectionEnd = selections[1];
2036
- }
2031
+ var sel = vim.sel;
2032
+ var head = actionArgs.head || cm.getCursor('head');
2033
+ var height = cm.listSelections().length;
2037
2034
  if (insertAt == 'eol') {
2038
- var cursor = cm.getCursor();
2039
- cursor = Pos(cursor.line, lineLength(cm, cursor.line));
2040
- cm.setCursor(cursor);
2035
+ head = Pos(head.line, lineLength(cm, head.line));
2041
2036
  } else if (insertAt == 'charAfter') {
2042
- cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
2037
+ head = offsetCursor(head, 0, 1);
2043
2038
  } else if (insertAt == 'firstNonBlank') {
2044
- if (vim.visualMode && !vim.visualBlock) {
2045
- if (selectionEnd.line < selectionStart.line) {
2046
- cm.setCursor(selectionEnd);
2039
+ head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head);
2040
+ } else if (insertAt == 'startOfSelectedArea') {
2041
+ if (!vim.visualBlock) {
2042
+ if (sel.head.line < sel.anchor.line) {
2043
+ head = sel.head;
2047
2044
  } else {
2048
- selectionStart = Pos(selectionStart.line, 0);
2049
- cm.setCursor(selectionStart);
2045
+ head = Pos(sel.anchor.line, 0);
2050
2046
  }
2051
- cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
2052
- } else if (vim.visualBlock) {
2053
- selectionEnd = Pos(selectionEnd.line, selectionStart.ch);
2054
- cm.setCursor(selectionStart);
2055
- selectBlock(cm, selectionEnd);
2056
2047
  } else {
2057
- cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
2048
+ head = Pos(
2049
+ Math.min(sel.head.line, sel.anchor.line),
2050
+ Math.min(sel.head.ch, sel.anchor.ch));
2051
+ height = Math.abs(sel.head.line - sel.anchor.line) + 1;
2058
2052
  }
2059
2053
  } else if (insertAt == 'endOfSelectedArea') {
2060
- if (vim.visualBlock) {
2061
- selectionStart = Pos(selectionStart.line, selectionEnd.ch);
2062
- cm.setCursor(selectionStart);
2063
- selectBlock(cm, selectionEnd);
2064
- } else if (selectionEnd.line < selectionStart.line) {
2065
- selectionEnd = Pos(selectionStart.line, 0);
2066
- cm.setCursor(selectionEnd);
2054
+ if (!vim.visualBlock) {
2055
+ if (sel.head.line >= sel.anchor.line) {
2056
+ head = offsetCursor(sel.head, 0, 1);
2057
+ } else {
2058
+ head = Pos(sel.anchor.line, 0);
2059
+ }
2060
+ } else {
2061
+ head = Pos(
2062
+ Math.min(sel.head.line, sel.anchor.line),
2063
+ Math.max(sel.head.ch + 1, sel.anchor.ch));
2064
+ height = Math.abs(sel.head.line - sel.anchor.line) + 1;
2067
2065
  }
2068
2066
  } else if (insertAt == 'inplace') {
2069
2067
  if (vim.visualMode){
@@ -2089,129 +2087,68 @@
2089
2087
  if (vim.visualMode) {
2090
2088
  exitVisualMode(cm);
2091
2089
  }
2090
+ selectForInsert(cm, head, height);
2092
2091
  },
2093
2092
  toggleVisualMode: function(cm, actionArgs, vim) {
2094
2093
  var repeat = actionArgs.repeat;
2095
- var curStart = cm.getCursor();
2096
- var curEnd;
2097
- var selections = cm.listSelections();
2094
+ var anchor = cm.getCursor();
2095
+ var head;
2098
2096
  // TODO: The repeat should actually select number of characters/lines
2099
2097
  // equal to the repeat times the size of the previous visual
2100
2098
  // operation.
2101
2099
  if (!vim.visualMode) {
2102
- cm.on('mousedown', exitVisualMode);
2100
+ // Entering visual mode
2103
2101
  vim.visualMode = true;
2104
2102
  vim.visualLine = !!actionArgs.linewise;
2105
2103
  vim.visualBlock = !!actionArgs.blockwise;
2106
- if (vim.visualLine) {
2107
- curStart.ch = 0;
2108
- curEnd = clipCursorToContent(
2109
- cm, Pos(curStart.line + repeat - 1, lineLength(cm, curStart.line)),
2110
- true /** includeLineBreak */);
2111
- } else {
2112
- curEnd = clipCursorToContent(
2113
- cm, Pos(curStart.line, curStart.ch + repeat),
2104
+ head = clipCursorToContent(
2105
+ cm, Pos(anchor.line, anchor.ch + repeat - 1),
2114
2106
  true /** includeLineBreak */);
2115
- }
2116
- cm.setSelection(curStart, curEnd);
2117
- CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
2107
+ vim.sel = {
2108
+ anchor: anchor,
2109
+ head: head
2110
+ };
2111
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
2112
+ updateCmSelection(cm);
2113
+ updateMark(cm, vim, '<', cursorMin(anchor, head));
2114
+ updateMark(cm, vim, '>', cursorMax(anchor, head));
2115
+ } else if (vim.visualLine ^ actionArgs.linewise ||
2116
+ vim.visualBlock ^ actionArgs.blockwise) {
2117
+ // Toggling between modes
2118
+ vim.visualLine = !!actionArgs.linewise;
2119
+ vim.visualBlock = !!actionArgs.blockwise;
2120
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
2121
+ updateCmSelection(cm);
2118
2122
  } else {
2119
- curStart = cm.getCursor('anchor');
2120
- curEnd = cm.getCursor('head');
2121
- if (vim.visualLine) {
2122
- if (actionArgs.blockwise) {
2123
- // This means Ctrl-V pressed in linewise visual
2124
- vim.visualBlock = true;
2125
- selectBlock(cm, curEnd);
2126
- CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'blockwise'});
2127
- } else if (!actionArgs.linewise) {
2128
- // v pressed in linewise, switch to characterwise visual mode
2129
- CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual'});
2130
- } else {
2131
- exitVisualMode(cm);
2132
- }
2133
- vim.visualLine = false;
2134
- } else if (vim.visualBlock) {
2135
- if (actionArgs.linewise) {
2136
- // Shift-V pressed in blockwise visual mode
2137
- vim.visualLine = true;
2138
- curStart = Pos(selections[0].anchor.line, 0);
2139
- curEnd = Pos(selections[selections.length-1].anchor.line, lineLength(cm, selections[selections.length-1].anchor.line));
2140
- cm.setSelection(curStart, curEnd);
2141
- CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'linewise'});
2142
- } else if (!actionArgs.blockwise) {
2143
- // v pressed in blockwise mode, Switch to characterwise
2144
- if (curEnd != selections[0].head) {
2145
- curStart = selections[0].anchor;
2146
- } else {
2147
- curStart = selections[selections.length-1].anchor;
2148
- }
2149
- cm.setSelection(curStart, curEnd);
2150
- CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual'});
2151
- } else {
2152
- exitVisualMode(cm);
2153
- }
2154
- vim.visualBlock = false;
2155
- } else if (actionArgs.linewise) {
2156
- // Shift-V pressed in characterwise visual mode. Switch to linewise
2157
- // visual mode instead of exiting visual mode.
2158
- vim.visualLine = true;
2159
- curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 :
2160
- lineLength(cm, curStart.line);
2161
- curEnd.ch = cursorIsBefore(curStart, curEnd) ?
2162
- lineLength(cm, curEnd.line) : 0;
2163
- cm.setSelection(curStart, curEnd);
2164
- CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"});
2165
- } else if (actionArgs.blockwise) {
2166
- vim.visualBlock = true;
2167
- selectBlock(cm, curEnd);
2168
- CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'blockwise'});
2169
- } else {
2170
- exitVisualMode(cm);
2171
- }
2123
+ exitVisualMode(cm);
2172
2124
  }
2173
- updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart
2174
- : curEnd);
2175
- updateMark(cm, vim, '>', cursorIsBefore(curStart, curEnd) ? curEnd
2176
- : curStart);
2177
2125
  },
2178
2126
  reselectLastSelection: function(cm, _actionArgs, vim) {
2179
- var curStart = vim.marks['<'].find();
2180
- var curEnd = vim.marks['>'].find();
2181
2127
  var lastSelection = vim.lastSelection;
2128
+ if (vim.visualMode) {
2129
+ updateLastSelection(cm, vim);
2130
+ }
2182
2131
  if (lastSelection) {
2183
- // Set the selections as per last selection
2184
- var selectionStart = lastSelection.curStartMark.find();
2185
- var selectionEnd = lastSelection.curEndMark.find();
2186
- var blockwise = lastSelection.visualBlock;
2187
- // update last selection
2188
- updateLastSelection(cm, vim, curStart, curEnd);
2189
- if (blockwise) {
2190
- cm.setCursor(selectionStart);
2191
- selectionStart = selectBlock(cm, selectionEnd);
2192
- } else {
2193
- cm.setSelection(selectionStart, selectionEnd);
2194
- selectionStart = cm.getCursor('anchor');
2195
- selectionEnd = cm.getCursor('head');
2196
- }
2197
- if (vim.visualMode) {
2198
- updateMark(cm, vim, '<', cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
2199
- : selectionEnd);
2200
- updateMark(cm, vim, '>', cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd
2201
- : selectionStart);
2132
+ var anchor = lastSelection.anchorMark.find();
2133
+ var head = lastSelection.headMark.find();
2134
+ if (!anchor || !head) {
2135
+ // If the marks have been destroyed due to edits, do nothing.
2136
+ return;
2202
2137
  }
2203
- // Last selection is updated now
2138
+ vim.sel = {
2139
+ anchor: anchor,
2140
+ head: head
2141
+ };
2204
2142
  vim.visualMode = true;
2205
- if (lastSelection.visualLine) {
2206
- vim.visualLine = true;
2207
- vim.visualBlock = false;
2208
- } else if (lastSelection.visualBlock) {
2209
- vim.visualLine = false;
2210
- vim.visualBlock = true;
2211
- } else {
2212
- vim.visualBlock = vim.visualLine = false;
2213
- }
2214
- CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
2143
+ vim.visualLine = lastSelection.visualLine;
2144
+ vim.visualBlock = lastSelection.visualBlock;
2145
+ updateCmSelection(cm);
2146
+ updateMark(cm, vim, '<', cursorMin(anchor, head));
2147
+ updateMark(cm, vim, '>', cursorMax(anchor, head));
2148
+ CodeMirror.signal(cm, 'vim-mode-change', {
2149
+ mode: 'visual',
2150
+ subMode: vim.visualLine ? 'linewise' :
2151
+ vim.visualBlock ? 'blockwise' : ''});
2215
2152
  }
2216
2153
  },
2217
2154
  joinLines: function(cm, actionArgs, vim) {
@@ -2228,18 +2165,19 @@
2228
2165
  Infinity));
2229
2166
  }
2230
2167
  var finalCh = 0;
2231
- cm.operation(function() {
2232
- for (var i = curStart.line; i < curEnd.line; i++) {
2233
- finalCh = lineLength(cm, curStart.line);
2234
- var tmp = Pos(curStart.line + 1,
2235
- lineLength(cm, curStart.line + 1));
2236
- var text = cm.getRange(curStart, tmp);
2237
- text = text.replace(/\n\s*/g, ' ');
2238
- cm.replaceRange(text, curStart, tmp);
2239
- }
2240
- var curFinalPos = Pos(curStart.line, finalCh);
2241
- cm.setCursor(curFinalPos);
2242
- });
2168
+ for (var i = curStart.line; i < curEnd.line; i++) {
2169
+ finalCh = lineLength(cm, curStart.line);
2170
+ var tmp = Pos(curStart.line + 1,
2171
+ lineLength(cm, curStart.line + 1));
2172
+ var text = cm.getRange(curStart, tmp);
2173
+ text = text.replace(/\n\s*/g, ' ');
2174
+ cm.replaceRange(text, curStart, tmp);
2175
+ }
2176
+ var curFinalPos = Pos(curStart.line, finalCh);
2177
+ cm.setCursor(curFinalPos);
2178
+ if (vim.visualMode) {
2179
+ exitVisualMode(cm);
2180
+ }
2243
2181
  },
2244
2182
  newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
2245
2183
  vim.insertMode = true;
@@ -2268,11 +2206,12 @@
2268
2206
  return;
2269
2207
  }
2270
2208
  if (actionArgs.matchIndent) {
2271
- // length that considers tabs and cm.options.tabSize
2209
+ var tabSize = cm.getOption("tabSize");
2210
+ // length that considers tabs and tabSize
2272
2211
  var whitespaceLength = function(str) {
2273
2212
  var tabs = (str.split("\t").length - 1);
2274
2213
  var spaces = (str.split(" ").length - 1);
2275
- return tabs * cm.options.tabSize + spaces * 1;
2214
+ return tabs * tabSize + spaces * 1;
2276
2215
  };
2277
2216
  var currentLine = cm.getLine(cm.getCursor().line);
2278
2217
  var indent = whitespaceLength(currentLine.match(/^\s*/)[0]);
@@ -2285,8 +2224,8 @@
2285
2224
  if (newIndent < 0) {
2286
2225
  return "";
2287
2226
  }
2288
- else if (cm.options.indentWithTabs) {
2289
- var quotient = Math.floor(newIndent / cm.options.tabSize);
2227
+ else if (cm.getOption("indentWithTabs")) {
2228
+ var quotient = Math.floor(newIndent / tabSize);
2290
2229
  return Array(quotient + 1).join('\t');
2291
2230
  }
2292
2231
  else {
@@ -2334,7 +2273,7 @@
2334
2273
  var emptyStrings = new Array(selections.length).join('1').split('1');
2335
2274
  // save the curEnd marker before it get cleared due to cm.replaceRange.
2336
2275
  if (vim.lastSelection) {
2337
- lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
2276
+ lastSelectionCurEnd = vim.lastSelection.headMark.find();
2338
2277
  }
2339
2278
  // push the previously selected text to unnamed register
2340
2279
  vimGlobalState.registerController.unnamedRegister.setText(selectedText);
@@ -2358,7 +2297,7 @@
2358
2297
  }
2359
2298
  // restore the the curEnd marker
2360
2299
  if(lastSelectionCurEnd) {
2361
- vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd);
2300
+ vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd);
2362
2301
  }
2363
2302
  if (linewise) {
2364
2303
  curPosFinal.ch=0;
@@ -2400,10 +2339,10 @@
2400
2339
  }
2401
2340
  }
2402
2341
  }
2403
- cm.setCursor(curPosFinal);
2404
2342
  if (vim.visualMode) {
2405
2343
  exitVisualMode(cm);
2406
2344
  }
2345
+ cm.setCursor(curPosFinal);
2407
2346
  },
2408
2347
  undo: function(cm, actionArgs) {
2409
2348
  cm.operation(function() {
@@ -2448,7 +2387,7 @@
2448
2387
  replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith);
2449
2388
  if (vim.visualBlock) {
2450
2389
  // Tabs are split in visua block before replacing
2451
- var spaces = new Array(cm.options.tabSize+1).join(' ');
2390
+ var spaces = new Array(cm.getOption("tabSize")+1).join(' ');
2452
2391
  replaceWithStr = cm.getSelection();
2453
2392
  replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n');
2454
2393
  cm.replaceSelections(replaceWithStr);
@@ -2504,28 +2443,6 @@
2504
2443
  }
2505
2444
  repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
2506
2445
  },
2507
- changeCase: function(cm, actionArgs, vim) {
2508
- var selectionStart = getSelectedAreaRange(cm, vim)[0];
2509
- var text = cm.getSelection();
2510
- var lastSelectionCurEnd;
2511
- var blockSelection;
2512
- if (vim.lastSelection) {
2513
- // save the curEnd marker to avoid its removal due to cm.replaceRange
2514
- lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
2515
- blockSelection = vim.lastSelection.visualBlock;
2516
- }
2517
- var toLower = actionArgs.toLower;
2518
- text = toLower ? text.toLowerCase() : text.toUpperCase();
2519
- cm.replaceSelections(vim.visualBlock || blockSelection ? text.split('\n') : [text]);
2520
- // restore the last selection curEnd marker
2521
- if (lastSelectionCurEnd) {
2522
- vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd);
2523
- }
2524
- cm.setCursor(selectionStart);
2525
- if (vim.visualMode) {
2526
- exitVisualMode(cm);
2527
- }
2528
- },
2529
2446
  exitInsertMode: exitInsertMode
2530
2447
  };
2531
2448
 
@@ -2554,8 +2471,18 @@
2554
2471
  return ret;
2555
2472
  }
2556
2473
  function offsetCursor(cur, offsetLine, offsetCh) {
2474
+ if (typeof offsetLine === 'object') {
2475
+ offsetCh = offsetLine.ch;
2476
+ offsetLine = offsetLine.line;
2477
+ }
2557
2478
  return Pos(cur.line + offsetLine, cur.ch + offsetCh);
2558
2479
  }
2480
+ function getOffset(anchor, head) {
2481
+ return {
2482
+ line: head.line - anchor.line,
2483
+ ch: head.line - anchor.line
2484
+ };
2485
+ }
2559
2486
  function commandMatches(keys, keyMap, context, inputState) {
2560
2487
  // Partial matches are not applied. They inform the key handler
2561
2488
  // that the current key sequence is a subsequence of a valid key
@@ -2628,9 +2555,15 @@
2628
2555
  return false;
2629
2556
  }
2630
2557
  function cursorMin(cur1, cur2) {
2558
+ if (arguments.length > 2) {
2559
+ cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1));
2560
+ }
2631
2561
  return cursorIsBefore(cur1, cur2) ? cur1 : cur2;
2632
2562
  }
2633
2563
  function cursorMax(cur1, cur2) {
2564
+ if (arguments.length > 2) {
2565
+ cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1));
2566
+ }
2634
2567
  return cursorIsBefore(cur1, cur2) ? cur2 : cur1;
2635
2568
  }
2636
2569
  function cursorIsBetween(cur1, cur2, cur3) {
@@ -2668,121 +2601,59 @@
2668
2601
  // Distance between selectionEnd.ch and any(first considered here) selection.ch
2669
2602
  function selectBlock(cm, selectionEnd) {
2670
2603
  var selections = [], ranges = cm.listSelections();
2671
- var firstRange = ranges[0].anchor, lastRange = ranges[ranges.length-1].anchor;
2672
- var start, end, direction, selectionStart;
2673
- var curEnd = cm.getCursor('head');
2674
- var originalSelectionEnd = copyCursor(selectionEnd);
2675
- start = firstRange.line;
2676
- end = lastRange.line;
2677
- if (selectionEnd.line < curEnd.line) {
2678
- direction = 'up';
2679
- } else if (selectionEnd.line > curEnd.line) {
2680
- direction = 'down';
2681
- } else {
2682
- if (selectionEnd.ch != curEnd.ch) {
2683
- direction = selectionEnd.ch > curEnd.ch ? 'right' : 'left';
2684
- }
2685
- selectionStart = cm.getCursor('anchor');
2686
- }
2687
- var primIndex = getIndex(ranges, curEnd);
2688
- // sets to true when selectionEnd already lies inside the existing selections
2689
- selectionEnd = cm.clipPos(selectionEnd);
2690
- var contains = getIndex(ranges, selectionEnd) < 0 ? false : true;
2691
- var isClipped = !cursorEqual(originalSelectionEnd, selectionEnd);
2692
- // This function helps to check selection crossing
2693
- // in case of short lines.
2694
- var processSelectionCrossing = function() {
2695
- if (isClipped) {
2696
- if (curEnd.ch >= selectionStart.ch) {
2697
- selectionStart.ch++;
2698
- }
2699
- } else if (curEnd.ch == lineLength(cm, curEnd.line)) {
2700
- if (cursorEqual(ranges[primIndex].anchor, ranges[primIndex].head) && ranges.length>1) {
2701
- if (direction == 'up') {
2702
- if (contains || primIndex>0) {
2703
- start = firstRange.line;
2704
- end = selectionEnd.line;
2705
- selectionStart = ranges[primIndex-1].anchor;
2706
- }
2707
- } else {
2708
- if (contains || primIndex == 0) {
2709
- end = lastRange.line;
2710
- start = selectionEnd.line;
2711
- selectionStart = ranges[primIndex+1].anchor;
2712
- }
2713
- }
2714
- if (selectionEnd.ch >= selectionStart.ch) {
2715
- selectionStart.ch--;
2716
- }
2717
- }
2718
- }
2719
- };
2720
- switch(direction) {
2721
- case 'up':
2722
- start = contains ? firstRange.line : selectionEnd.line;
2723
- end = contains ? selectionEnd.line : lastRange.line;
2724
- selectionStart = lastRange;
2725
- processSelectionCrossing();
2726
- break;
2727
- case 'down':
2728
- start = contains ? selectionEnd.line : firstRange.line;
2729
- end = contains ? lastRange.line : selectionEnd.line;
2730
- selectionStart = firstRange;
2731
- processSelectionCrossing();
2732
- break;
2733
- case 'left':
2734
- if ((selectionEnd.ch <= selectionStart.ch) && (curEnd.ch > selectionStart.ch)) {
2735
- selectionStart.ch++;
2736
- selectionEnd.ch--;
2737
- }
2738
- break;
2739
- case 'right':
2740
- if ((selectionStart.ch <= selectionEnd.ch) && (curEnd.ch < selectionStart.ch)) {
2741
- selectionStart.ch--;
2742
- selectionEnd.ch++;
2743
- }
2744
- break;
2745
- default:
2746
- start = selectionStart.line;
2747
- end = selectionEnd.line;
2748
- }
2749
- while (start <= end) {
2750
- var anchor = {line: start, ch: selectionStart.ch};
2751
- var head = {line: start, ch: selectionEnd.ch};
2752
- var range = {anchor: anchor, head: head};
2604
+ var head = copyCursor(cm.clipPos(selectionEnd));
2605
+ var isClipped = !cursorEqual(selectionEnd, head);
2606
+ var curHead = cm.getCursor('head');
2607
+ var primIndex = getIndex(ranges, curHead);
2608
+ var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor);
2609
+ var max = ranges.length - 1;
2610
+ var index = max - primIndex > primIndex ? max : 0;
2611
+ var base = ranges[index].anchor;
2612
+
2613
+ var firstLine = Math.min(base.line, head.line);
2614
+ var lastLine = Math.max(base.line, head.line);
2615
+ var baseCh = base.ch, headCh = head.ch;
2616
+
2617
+ var dir = ranges[index].head.ch - baseCh;
2618
+ var newDir = headCh - baseCh;
2619
+ if (dir > 0 && newDir <= 0) {
2620
+ baseCh++;
2621
+ if (!isClipped) { headCh--; }
2622
+ } else if (dir < 0 && newDir >= 0) {
2623
+ baseCh--;
2624
+ if (!wasClipped) { headCh++; }
2625
+ } else if (dir < 0 && newDir == -1) {
2626
+ baseCh--;
2627
+ headCh++;
2628
+ }
2629
+ for (var line = firstLine; line <= lastLine; line++) {
2630
+ var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)};
2753
2631
  selections.push(range);
2754
- if (cursorEqual(head, selectionEnd)) {
2755
- primIndex = selections.indexOf(range);
2756
- }
2757
- start++;
2758
2632
  }
2759
- // Update selectionEnd and selectionStart
2760
- // after selection crossing
2761
- selectionEnd.ch = selections[0].head.ch;
2762
- selectionStart.ch = selections[0].anchor.ch;
2763
- if (cursorEqual(selectionEnd, selections[0].head)) {
2764
- selectionStart.line = selections[selections.length-1].anchor.line;
2765
- } else {
2766
- selectionStart.line = selections[0].anchor.line;
2633
+ primIndex = head.line == lastLine ? selections.length - 1 : 0;
2634
+ cm.setSelections(selections);
2635
+ selectionEnd.ch = headCh;
2636
+ base.ch = baseCh;
2637
+ return base;
2638
+ }
2639
+ function selectForInsert(cm, head, height) {
2640
+ var sel = [];
2641
+ for (var i = 0; i < height; i++) {
2642
+ var lineHead = offsetCursor(head, i, 0);
2643
+ sel.push({anchor: lineHead, head: lineHead});
2767
2644
  }
2768
- cm.setSelections(selections, primIndex);
2769
- return selectionStart;
2645
+ cm.setSelections(sel, 0);
2770
2646
  }
2771
2647
  // getIndex returns the index of the cursor in the selections.
2772
2648
  function getIndex(ranges, cursor, end) {
2773
- var pos = -1;
2774
2649
  for (var i = 0; i < ranges.length; i++) {
2775
- var atAnchor = cursorEqual(ranges[i].anchor, cursor);
2776
- var atHead = cursorEqual(ranges[i].head, cursor);
2777
- if (end == 'head') {
2778
- pos = atHead ? i : pos;
2779
- } else if (end == 'anchor') {
2780
- pos = atAnchor ? i : pos;
2781
- } else {
2782
- pos = (atAnchor || atHead) ? i : pos;
2650
+ var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor);
2651
+ var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor);
2652
+ if (atAnchor || atHead) {
2653
+ return i;
2783
2654
  }
2784
2655
  }
2785
- return pos;
2656
+ return -1;
2786
2657
  }
2787
2658
  function getSelectedAreaRange(cm, vim) {
2788
2659
  var lastSelection = vim.lastSelection;
@@ -2813,8 +2684,8 @@
2813
2684
  }
2814
2685
  cm.setSelections(selections);
2815
2686
  } else {
2816
- var start = lastSelection.curStartMark.find();
2817
- var end = lastSelection.curEndMark.find();
2687
+ var start = lastSelection.anchorMark.find();
2688
+ var end = lastSelection.headMark.find();
2818
2689
  var line = end.line - start.line;
2819
2690
  var ch = end.ch - start.ch;
2820
2691
  selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch};
@@ -2833,36 +2704,28 @@
2833
2704
  return getCurrentSelectedAreaRange();
2834
2705
  }
2835
2706
  }
2836
- function updateLastSelection(cm, vim, selectionStart, selectionEnd) {
2837
- if (!selectionStart || !selectionEnd) {
2838
- selectionStart = vim.marks['<'].find() || cm.getCursor('anchor');
2839
- selectionEnd = vim.marks['>'].find() || cm.getCursor('head');
2840
- }
2707
+ // Updates the previous selection with the current selection's values. This
2708
+ // should only be called in visual mode.
2709
+ function updateLastSelection(cm, vim) {
2710
+ var anchor = vim.sel.anchor;
2711
+ var head = vim.sel.head;
2841
2712
  // To accommodate the effect of lastPastedText in the last selection
2842
2713
  if (vim.lastPastedText) {
2843
- selectionEnd = cm.posFromIndex(cm.indexFromPos(selectionStart) + vim.lastPastedText.length);
2714
+ head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length);
2844
2715
  vim.lastPastedText = null;
2845
2716
  }
2846
- var ranges = cm.listSelections();
2847
- // This check ensures to set the cursor
2848
- // position where we left off in previous selection
2849
- var swap = getIndex(ranges, selectionStart, 'head') > -1;
2850
- if (vim.visualBlock) {
2851
- var height = Math.abs(selectionStart.line - selectionEnd.line)+1;
2852
- var width = Math.abs(selectionStart.ch - selectionEnd.ch);
2853
- var block = {height: height, width: width};
2854
- }
2855
- // can't use selection state here because yank has already reset its cursor
2856
- // Also, Bookmarks make the visual selections robust to edit operations
2857
- vim.lastSelection = {'curStartMark': cm.setBookmark(swap ? selectionEnd : selectionStart),
2858
- 'curEndMark': cm.setBookmark(swap ? selectionStart : selectionEnd),
2717
+ vim.lastSelection = {'anchorMark': cm.setBookmark(anchor),
2718
+ 'headMark': cm.setBookmark(head),
2719
+ 'anchor': copyCursor(anchor),
2720
+ 'head': copyCursor(head),
2859
2721
  'visualMode': vim.visualMode,
2860
2722
  'visualLine': vim.visualLine,
2861
- 'visualBlock': block};
2723
+ 'visualBlock': vim.visualBlock};
2862
2724
  }
2863
2725
  function expandSelection(cm, start, end) {
2864
- var head = cm.getCursor('head');
2865
- var anchor = cm.getCursor('anchor');
2726
+ var sel = cm.state.vim.sel;
2727
+ var head = sel.head;
2728
+ var anchor = sel.anchor;
2866
2729
  var tmp;
2867
2730
  if (cursorIsBefore(end, start)) {
2868
2731
  tmp = end;
@@ -2875,9 +2738,75 @@
2875
2738
  } else {
2876
2739
  anchor = cursorMin(start, anchor);
2877
2740
  head = cursorMax(head, end);
2741
+ head = offsetCursor(head, 0, -1);
2742
+ if (head.ch == -1 && head.line != cm.firstLine()) {
2743
+ head = Pos(head.line - 1, lineLength(cm, head.line - 1));
2744
+ }
2878
2745
  }
2879
2746
  return [anchor, head];
2880
2747
  }
2748
+ /**
2749
+ * Updates the CodeMirror selection to match the provided vim selection.
2750
+ * If no arguments are given, it uses the current vim selection state.
2751
+ */
2752
+ function updateCmSelection(cm, sel, mode) {
2753
+ var vim = cm.state.vim;
2754
+ sel = sel || vim.sel;
2755
+ var mode = mode ||
2756
+ vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char';
2757
+ var cmSel = makeCmSelection(cm, sel, mode);
2758
+ cm.setSelections(cmSel.ranges, cmSel.primary);
2759
+ updateFakeCursor(cm);
2760
+ }
2761
+ function makeCmSelection(cm, sel, mode, exclusive) {
2762
+ var head = copyCursor(sel.head);
2763
+ var anchor = copyCursor(sel.anchor);
2764
+ if (mode == 'char') {
2765
+ var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
2766
+ var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
2767
+ head = offsetCursor(sel.head, 0, headOffset);
2768
+ anchor = offsetCursor(sel.anchor, 0, anchorOffset);
2769
+ return {
2770
+ ranges: [{anchor: anchor, head: head}],
2771
+ primary: 0
2772
+ };
2773
+ } else if (mode == 'line') {
2774
+ if (!cursorIsBefore(sel.head, sel.anchor)) {
2775
+ anchor.ch = 0;
2776
+
2777
+ var lastLine = cm.lastLine();
2778
+ if (head.line > lastLine) {
2779
+ head.line = lastLine;
2780
+ }
2781
+ head.ch = lineLength(cm, head.line);
2782
+ } else {
2783
+ head.ch = 0;
2784
+ anchor.ch = lineLength(cm, anchor.line);
2785
+ }
2786
+ return {
2787
+ ranges: [{anchor: anchor, head: head}],
2788
+ primary: 0
2789
+ };
2790
+ } else if (mode == 'block') {
2791
+ var top = Math.min(anchor.line, head.line),
2792
+ left = Math.min(anchor.ch, head.ch),
2793
+ bottom = Math.max(anchor.line, head.line),
2794
+ right = Math.max(anchor.ch, head.ch) + 1;
2795
+ var height = bottom - top + 1;
2796
+ var primary = head.line == top ? 0 : height - 1;
2797
+ var ranges = [];
2798
+ for (var i = 0; i < height; i++) {
2799
+ ranges.push({
2800
+ anchor: Pos(top + i, left),
2801
+ head: Pos(top + i, right)
2802
+ });
2803
+ }
2804
+ return {
2805
+ ranges: ranges,
2806
+ primary: primary
2807
+ };
2808
+ }
2809
+ }
2881
2810
  function getHead(cm) {
2882
2811
  var cur = cm.getCursor('head');
2883
2812
  if (cm.getSelection().length == 1) {
@@ -2888,23 +2817,20 @@
2888
2817
  return cur;
2889
2818
  }
2890
2819
 
2891
- function exitVisualMode(cm) {
2892
- cm.off('mousedown', exitVisualMode);
2820
+ /**
2821
+ * If moveHead is set to false, the CodeMirror selection will not be
2822
+ * touched. The caller assumes the responsibility of putting the cursor
2823
+ * in the right place.
2824
+ */
2825
+ function exitVisualMode(cm, moveHead) {
2893
2826
  var vim = cm.state.vim;
2894
- var selectionStart = cm.getCursor('anchor');
2895
- var selectionEnd = cm.getCursor('head');
2896
- // hack to place the cursor at the right place
2897
- // in case of visual block
2898
- if (vim.visualBlock && (cursorIsBefore(selectionStart, selectionEnd))) {
2899
- selectionEnd.ch--;
2827
+ if (moveHead !== false) {
2828
+ cm.setCursor(clipCursorToContent(cm, vim.sel.head));
2900
2829
  }
2901
2830
  updateLastSelection(cm, vim);
2902
2831
  vim.visualMode = false;
2903
2832
  vim.visualLine = false;
2904
2833
  vim.visualBlock = false;
2905
- if (!cursorEqual(selectionStart, selectionEnd)) {
2906
- cm.setCursor(clipCursorToContent(cm, selectionEnd));
2907
- }
2908
2834
  CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
2909
2835
  if (vim.fakeCursor) {
2910
2836
  vim.fakeCursor.clear();
@@ -3234,6 +3160,7 @@
3234
3160
 
3235
3161
  /**
3236
3162
  * @param {CodeMirror} cm CodeMirror object.
3163
+ * @param {Pos} cur The position to start from.
3237
3164
  * @param {int} repeat Number of words to move past.
3238
3165
  * @param {boolean} forward True to search forward. False to search
3239
3166
  * backward.
@@ -3243,8 +3170,7 @@
3243
3170
  * False if only alphabet characters count as part of the word.
3244
3171
  * @return {Cursor} The position the cursor should move to.
3245
3172
  */
3246
- function moveToWord(cm, repeat, forward, wordEnd, bigWord) {
3247
- var cur = cm.getCursor();
3173
+ function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) {
3248
3174
  var curStart = copyCursor(cur);
3249
3175
  var words = [];
3250
3176
  if (forward && !wordEnd || !forward && wordEnd) {
@@ -3344,8 +3270,8 @@
3344
3270
 
3345
3271
  // TODO: perhaps this finagling of start and end positions belonds
3346
3272
  // in codmirror/replaceRange?
3347
- function selectCompanionObject(cm, symb, inclusive) {
3348
- var cur = getHead(cm), start, end;
3273
+ function selectCompanionObject(cm, head, symb, inclusive) {
3274
+ var cur = head, start, end;
3349
3275
 
3350
3276
  var bracketRegexp = ({
3351
3277
  '(': /[()]/, ')': /[()]/,
@@ -3389,8 +3315,8 @@
3389
3315
  // Takes in a symbol and a cursor and tries to simulate text objects that
3390
3316
  // have identical opening and closing symbols
3391
3317
  // TODO support across multiple lines
3392
- function findBeginningAndEnd(cm, symb, inclusive) {
3393
- var cur = copyCursor(getHead(cm));
3318
+ function findBeginningAndEnd(cm, head, symb, inclusive) {
3319
+ var cur = copyCursor(head);
3394
3320
  var line = cm.getLine(cur.line);
3395
3321
  var chars = line.split('');
3396
3322
  var start, end, i, len;
@@ -3838,10 +3764,10 @@
3838
3764
  { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true },
3839
3765
  { name: 'global', shortName: 'g' }
3840
3766
  ];
3841
- Vim.ExCommandDispatcher = function() {
3767
+ var ExCommandDispatcher = function() {
3842
3768
  this.buildCommandMap_();
3843
3769
  };
3844
- Vim.ExCommandDispatcher.prototype = {
3770
+ ExCommandDispatcher.prototype = {
3845
3771
  processCommand: function(cm, input, opt_params) {
3846
3772
  var vim = cm.state.vim;
3847
3773
  var commandHistoryRegister = vimGlobalState.registerController.getRegister(':');
@@ -4440,7 +4366,7 @@
4440
4366
  }
4441
4367
  };
4442
4368
 
4443
- var exCommandDispatcher = new Vim.ExCommandDispatcher();
4369
+ var exCommandDispatcher = new ExCommandDispatcher();
4444
4370
 
4445
4371
  /**
4446
4372
  * @param {CodeMirror} cm CodeMirror instance we are in.
@@ -4528,6 +4454,7 @@
4528
4454
  break;
4529
4455
  }
4530
4456
  if (done) { stop(close); }
4457
+ return true;
4531
4458
  }
4532
4459
 
4533
4460
  // Actually do replace.
@@ -4548,9 +4475,9 @@
4548
4475
  }
4549
4476
 
4550
4477
  CodeMirror.keyMap.vim = {
4551
- 'nofallthrough': true,
4552
- 'style': 'fat-cursor'
4553
- };
4478
+ attach: attachVimMap,
4479
+ detach: detachVimMap
4480
+ };
4554
4481
 
4555
4482
  function exitInsertMode(cm) {
4556
4483
  var vim = cm.state.vim;
@@ -4620,16 +4547,22 @@
4620
4547
  CodeMirror.commands.newlineAndIndent;
4621
4548
  fn(cm);
4622
4549
  },
4623
- fallthrough: ['default']
4550
+ fallthrough: ['default'],
4551
+ attach: attachVimMap,
4552
+ detach: detachVimMap
4624
4553
  };
4625
4554
 
4626
4555
  CodeMirror.keyMap['await-second'] = {
4627
- fallthrough: ['vim-insert']
4556
+ fallthrough: ['vim-insert'],
4557
+ attach: attachVimMap,
4558
+ detach: detachVimMap
4628
4559
  };
4629
4560
 
4630
4561
  CodeMirror.keyMap['vim-replace'] = {
4631
4562
  'Backspace': 'goCharLeft',
4632
- fallthrough: ['vim-insert']
4563
+ fallthrough: ['vim-insert'],
4564
+ attach: attachVimMap,
4565
+ detach: detachVimMap
4633
4566
  };
4634
4567
 
4635
4568
  function executeMacroRegister(cm, vim, macroModeState, registerName) {
@@ -4724,41 +4657,49 @@
4724
4657
  // Cursor moved outside the context of an edit. Reset the change.
4725
4658
  lastChange.changes = [];
4726
4659
  }
4727
- } else if (cm.doc.history.lastSelOrigin == '*mouse') {
4728
- // Reset lastHPos if mouse click was done in normal mode.
4729
- vim.lastHPos = cm.doc.getCursor().ch;
4660
+ } else if (!cm.curOp.isVimOp) {
4730
4661
  handleExternalSelection(cm, vim);
4731
4662
  }
4732
4663
  if (vim.visualMode) {
4733
- var from, head;
4734
- from = head = cm.getCursor('head');
4735
- var anchor = cm.getCursor('anchor');
4736
- var to = Pos(head.line, from.ch + (cursorIsBefore(anchor, head) ? -1 : 1));
4737
- if (cursorIsBefore(to, from)) {
4738
- var temp = from;
4739
- from = to;
4740
- to = temp;
4741
- }
4742
- if (vim.fakeCursor) {
4743
- vim.fakeCursor.clear();
4744
- }
4745
- vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'});
4664
+ updateFakeCursor(cm);
4746
4665
  }
4747
4666
  }
4748
-
4667
+ function updateFakeCursor(cm) {
4668
+ var vim = cm.state.vim;
4669
+ var from = copyCursor(vim.sel.head);
4670
+ var to = offsetCursor(from, 0, 1);
4671
+ if (vim.fakeCursor) {
4672
+ vim.fakeCursor.clear();
4673
+ }
4674
+ vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'});
4675
+ }
4749
4676
  function handleExternalSelection(cm, vim) {
4750
4677
  var anchor = cm.getCursor('anchor');
4751
4678
  var head = cm.getCursor('head');
4752
- // Enter visual mode when the mouse selects text.
4753
- if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
4679
+ // Enter or exit visual mode to match mouse selection.
4680
+ if (vim.visualMode && cursorEqual(head, anchor) && lineLength(cm, head.line) > head.ch) {
4681
+ exitVisualMode(cm, false);
4682
+ } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
4754
4683
  vim.visualMode = true;
4755
4684
  vim.visualLine = false;
4756
4685
  CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
4757
- cm.on('mousedown', exitVisualMode);
4758
4686
  }
4759
4687
  if (vim.visualMode) {
4688
+ // Bind CodeMirror selection model to vim selection model.
4689
+ // Mouse selections are considered visual characterwise.
4690
+ var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0;
4691
+ var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0;
4692
+ head = offsetCursor(head, 0, headOffset);
4693
+ anchor = offsetCursor(anchor, 0, anchorOffset);
4694
+ vim.sel = {
4695
+ anchor: anchor,
4696
+ head: head
4697
+ };
4760
4698
  updateMark(cm, vim, '<', cursorMin(head, anchor));
4761
4699
  updateMark(cm, vim, '>', cursorMax(head, anchor));
4700
+ } else if (!vim.insertMode) {
4701
+ // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse.
4702
+ vim.lastHPos = cm.getCursor().ch;
4762
4703
  }
4763
4704
  }
4764
4705
 
@@ -4781,7 +4722,7 @@
4781
4722
  return true;
4782
4723
  }
4783
4724
  if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
4784
- CodeMirror.lookupKey(keyName, ['vim-insert'], onKeyFound);
4725
+ CodeMirror.lookupKey(keyName, 'vim-insert', onKeyFound);
4785
4726
  }
4786
4727
  }
4787
4728
 
@@ -4850,32 +4791,33 @@
4850
4791
  }
4851
4792
  return true;
4852
4793
  }
4853
- var curStart = cm.getCursor();
4794
+ var head = cm.getCursor('head');
4854
4795
  var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock;
4855
4796
  if (inVisualBlock) {
4856
4797
  // Set up block selection again for repeating the changes.
4857
4798
  var vim = cm.state.vim;
4858
- var block = vim.lastSelection.visualBlock;
4859
- var curEnd = Pos(curStart.line + block.height-1, curStart.ch);
4860
- cm.setCursor(curStart);
4861
- selectBlock(cm, curEnd);
4799
+ var lastSel = vim.lastSelection;
4800
+ var offset = getOffset(lastSel.anchor, lastSel.head);
4801
+ selectForInsert(cm, head, offset.line + 1);
4862
4802
  repeat = cm.listSelections().length;
4863
- cm.setCursor(curStart);
4803
+ cm.setCursor(head);
4864
4804
  }
4865
4805
  for (var i = 0; i < repeat; i++) {
4806
+ if (inVisualBlock) {
4807
+ cm.setCursor(offsetCursor(head, i, 0));
4808
+ }
4866
4809
  for (var j = 0; j < changes.length; j++) {
4867
4810
  var change = changes[j];
4868
4811
  if (change instanceof InsertModeKey) {
4869
- CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler);
4812
+ CodeMirror.lookupKey(change.keyName, 'vim-insert', keyHandler);
4870
4813
  } else {
4871
4814
  var cur = cm.getCursor();
4872
4815
  cm.replaceRange(change, cur, cur);
4873
4816
  }
4874
4817
  }
4875
- if (inVisualBlock) {
4876
- curStart.line++;
4877
- cm.setCursor(curStart);
4878
- }
4818
+ }
4819
+ if (inVisualBlock) {
4820
+ cm.setCursor(offsetCursor(head, 0, 1));
4879
4821
  }
4880
4822
  }
4881
4823