codemirror-rails 4.7 → 4.8

Sign up to get free protection for your applications and to get access to all the features.
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