codemirror-rails 4.3 → 4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -153,6 +153,17 @@
153
153
  !/comment/.test(self.getTokenTypeAt(Pos(end, close + 1))))
154
154
  return false;
155
155
 
156
+ // Avoid killing block comments completely outside the selection.
157
+ // Positions of the last startString before the start of the selection, and the first endString after it.
158
+ var lastStart = startLine.lastIndexOf(startString, from.ch);
159
+ var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length);
160
+ if (lastStart != -1 && firstEnd != -1) return false;
161
+ // Positions of the first endString after the end of the selection, and the last startString before it.
162
+ firstEnd = endLine.indexOf(endString, to.ch);
163
+ var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch);
164
+ lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart;
165
+ if (firstEnd != -1 && lastStart != -1) return false;
166
+
156
167
  self.operation(function() {
157
168
  self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
158
169
  Pos(end, close + endString.length));
@@ -36,6 +36,22 @@
36
36
  return str.length == 2 ? str : null;
37
37
  }
38
38
 
39
+ // Project the token type that will exists after the given char is
40
+ // typed, and use it to determine whether it would cause the start
41
+ // of a string token.
42
+ function enteringString(cm, pos, ch) {
43
+ var line = cm.getLine(pos.line);
44
+ var token = cm.getTokenAt(pos);
45
+ if (/\bstring2?\b/.test(token.type)) return false;
46
+ var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
47
+ stream.pos = stream.start = token.start;
48
+ for (;;) {
49
+ var type1 = cm.getMode().token(stream, token.state);
50
+ if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
51
+ stream.start = stream.pos;
52
+ }
53
+ }
54
+
39
55
  function buildKeymap(pairs) {
40
56
  var map = {
41
57
  name : "autoCloseBrackets",
@@ -61,8 +77,6 @@
61
77
  var ranges = cm.listSelections(), type, next;
62
78
  for (var i = 0; i < ranges.length; i++) {
63
79
  var range = ranges[i], cur = range.head, curType;
64
- if (left == "'" && cm.getTokenTypeAt(cur) == "comment")
65
- return CodeMirror.Pass;
66
80
  var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
67
81
  if (!range.empty())
68
82
  curType = "surround";
@@ -75,9 +89,10 @@
75
89
  cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left &&
76
90
  (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != left))
77
91
  curType = "addFour";
78
- else if (left == right && CodeMirror.isWordChar(next))
79
- return CodeMirror.Pass;
80
- else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next))
92
+ else if (left == '"' || left == "'") {
93
+ if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, left)) curType = "both";
94
+ else return CodeMirror.Pass;
95
+ } else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next))
81
96
  curType = "both";
82
97
  else
83
98
  return CodeMirror.Pass;
@@ -228,9 +228,9 @@
228
228
  (completion.options.container || document.body).appendChild(hints);
229
229
  var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
230
230
  if (overlapY > 0) {
231
- var height = box.bottom - box.top, curTop = box.top - (pos.bottom - pos.top);
231
+ var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
232
232
  if (curTop - height > 0) { // Fits above cursor
233
- hints.style.top = (top = curTop - height) + "px";
233
+ hints.style.top = (top = pos.top - height) + "px";
234
234
  below = false;
235
235
  } else if (height > winH) {
236
236
  hints.style.height = (winH - 5) + "px";
@@ -11,7 +11,6 @@
11
11
  })(function(CodeMirror) {
12
12
  "use strict";
13
13
  var GUTTER_ID = "CodeMirror-lint-markers";
14
- var SEVERITIES = /^(?:error|warning)$/;
15
14
 
16
15
  function showTooltip(e, content) {
17
16
  var tt = document.createElement("div");
@@ -110,7 +109,7 @@
110
109
 
111
110
  function annotationTooltip(ann) {
112
111
  var severity = ann.severity;
113
- if (!SEVERITIES.test(severity)) severity = "error";
112
+ if (!severity) severity = "error";
114
113
  var tip = document.createElement("div");
115
114
  tip.className = "CodeMirror-lint-message-" + severity;
116
115
  tip.appendChild(document.createTextNode(ann.message));
@@ -141,7 +140,7 @@
141
140
  for (var i = 0; i < anns.length; ++i) {
142
141
  var ann = anns[i];
143
142
  var severity = ann.severity;
144
- if (!SEVERITIES.test(severity)) severity = "error";
143
+ if (!severity) severity = "error";
145
144
  maxSeverity = getMaxSeverity(maxSeverity, severity);
146
145
 
147
146
  if (options.formatAnnotation) ann = options.formatAnnotation(ann);
@@ -110,6 +110,7 @@
110
110
  var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
111
111
  var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
112
112
  function replace(cm, all) {
113
+ if (cm.getOption("readOnly")) return;
113
114
  dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
114
115
  if (!query) return;
115
116
  query = parseQuery(query);
@@ -107,7 +107,7 @@
107
107
  var from = Pos(pos.line, cut);
108
108
  for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
109
109
  if (target[i] != fold(doc.getLine(ln))) return;
110
- if (doc.getLine(ln).slice(0, origTarget[last].length) != target[last]) return;
110
+ if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
111
111
  return {from: from, to: Pos(ln, origTarget[last].length)};
112
112
  }
113
113
  };
@@ -481,7 +481,7 @@
481
481
  });
482
482
  };
483
483
 
484
- function findAndGoTo(cm, forward) {
484
+ function getTarget(cm) {
485
485
  var from = cm.getCursor("from"), to = cm.getCursor("to");
486
486
  if (CodeMirror.cmpPos(from, to) == 0) {
487
487
  var word = wordAt(cm, from);
@@ -489,9 +489,14 @@
489
489
  from = word.from;
490
490
  to = word.to;
491
491
  }
492
+ return {from: from, to: to, query: cm.getRange(from, to), word: word};
493
+ }
492
494
 
493
- var query = cm.getRange(from, to);
494
- var cur = cm.getSearchCursor(query, forward ? to : from);
495
+ function findAndGoTo(cm, forward) {
496
+ var target = getTarget(cm);
497
+ if (!target) return;
498
+ var query = target.query;
499
+ var cur = cm.getSearchCursor(query, forward ? target.to : target.from);
495
500
 
496
501
  if (forward ? cur.findNext() : cur.findPrevious()) {
497
502
  cm.setSelection(cur.from(), cur.to());
@@ -500,12 +505,25 @@
500
505
  : cm.clipPos(Pos(cm.lastLine())));
501
506
  if (forward ? cur.findNext() : cur.findPrevious())
502
507
  cm.setSelection(cur.from(), cur.to());
503
- else if (word)
504
- cm.setSelection(from, to);
508
+ else if (target.word)
509
+ cm.setSelection(target.from, target.to);
505
510
  }
506
511
  };
507
512
  cmds[map[ctrl + "F3"] = "findUnder"] = function(cm) { findAndGoTo(cm, true); };
508
513
  cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); };
514
+ cmds[map["Alt-F3"] = "findAllUnder"] = function(cm) {
515
+ var target = getTarget(cm);
516
+ if (!target) return;
517
+ var cur = cm.getSearchCursor(target.query);
518
+ var matches = [];
519
+ var primaryIndex = -1;
520
+ while (cur.findNext()) {
521
+ matches.push({anchor: cur.from(), head: cur.to()});
522
+ if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch)
523
+ primaryIndex++;
524
+ }
525
+ cm.setSelections(matches, primaryIndex);
526
+ };
509
527
 
510
528
  map["Shift-" + ctrl + "["] = "fold";
511
529
  map["Shift-" + ctrl + "]"] = "unfold";
@@ -225,7 +225,8 @@
225
225
  { keys: ['|'], type: 'motion',
226
226
  motion: 'moveToColumn',
227
227
  motionArgs: { }},
228
- { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { },context:'visual'},
228
+ { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { }, context:'visual'},
229
+ { keys: ['O'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'},
229
230
  // Operators
230
231
  { keys: ['d'], type: 'operator', operator: 'delete' },
231
232
  { keys: ['y'], type: 'operator', operator: 'yank' },
@@ -288,6 +289,8 @@
288
289
  { keys: ['v'], type: 'action', action: 'toggleVisualMode' },
289
290
  { keys: ['V'], type: 'action', action: 'toggleVisualMode',
290
291
  actionArgs: { linewise: true }},
292
+ { keys: ['<C-v>'], type: 'action', action: 'toggleVisualMode',
293
+ actionArgs: { blockwise: true }},
291
294
  { keys: ['g', 'v'], type: 'action', action: 'reselectLastSelection' },
292
295
  { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true },
293
296
  { keys: ['p'], type: 'action', action: 'paste', isEdit: true,
@@ -558,7 +561,9 @@
558
561
  MacroModeState.prototype = {
559
562
  exitMacroRecordMode: function() {
560
563
  var macroModeState = vimGlobalState.macroModeState;
561
- macroModeState.onRecordingDone(); // close dialog
564
+ if (macroModeState.onRecordingDone) {
565
+ macroModeState.onRecordingDone(); // close dialog
566
+ }
562
567
  macroModeState.onRecordingDone = undefined;
563
568
  macroModeState.isRecording = false;
564
569
  },
@@ -568,8 +573,10 @@
568
573
  if (register) {
569
574
  register.clear();
570
575
  this.latestRegister = registerName;
571
- this.onRecordingDone = cm.openDialog(
572
- '(recording)['+registerName+']', null, {bottom:true});
576
+ if (cm.openDialog) {
577
+ this.onRecordingDone = cm.openDialog(
578
+ '(recording)['+registerName+']', null, {bottom:true});
579
+ }
573
580
  this.isRecording = true;
574
581
  }
575
582
  }
@@ -607,6 +614,7 @@
607
614
  visualMode: false,
608
615
  // If we are in visual line mode. No effect if visualMode is false.
609
616
  visualLine: false,
617
+ visualBlock: false,
610
618
  lastSelection: null,
611
619
  lastPastedText: null
612
620
  };
@@ -1343,19 +1351,28 @@
1343
1351
  if (vim.visualMode) {
1344
1352
  // Check if the selection crossed over itself. Will need to shift
1345
1353
  // the start point if that happened.
1354
+ // offset is set to -1 or 1 to shift the curEnd
1355
+ // left or right
1356
+ var offset = 0;
1346
1357
  if (cursorIsBefore(selectionStart, selectionEnd) &&
1347
1358
  (cursorEqual(selectionStart, curEnd) ||
1348
1359
  cursorIsBefore(curEnd, selectionStart))) {
1349
1360
  // The end of the selection has moved from after the start to
1350
1361
  // before the start. We will shift the start right by 1.
1351
1362
  selectionStart.ch += 1;
1352
- curEnd.ch -= 1;
1363
+ offset = -1;
1353
1364
  } else if (cursorIsBefore(selectionEnd, selectionStart) &&
1354
1365
  (cursorEqual(selectionStart, curEnd) ||
1355
1366
  cursorIsBefore(selectionStart, curEnd))) {
1356
1367
  // The opposite happened. We will shift the start left by 1.
1357
1368
  selectionStart.ch -= 1;
1358
- curEnd.ch += 1;
1369
+ offset = 1;
1370
+ }
1371
+ // in case of visual Block selectionStart and curEnd
1372
+ // may not be on the same line,
1373
+ // Also, In case of v_o this should not happen.
1374
+ if (!vim.visualBlock && !(motionResult instanceof Array)) {
1375
+ curEnd.ch += offset;
1359
1376
  }
1360
1377
  if (vim.lastHPos != Infinity) {
1361
1378
  vim.lastHPos = curEnd.ch;
@@ -1375,8 +1392,14 @@
1375
1392
  selectionEnd.ch = 0;
1376
1393
  selectionStart.ch = lineLength(cm, selectionStart.line);
1377
1394
  }
1395
+ } else if (vim.visualBlock) {
1396
+ // Select a block and
1397
+ // return the diagonally opposite end.
1398
+ selectionStart = selectBlock(cm, selectionEnd);
1399
+ }
1400
+ if (!vim.visualBlock) {
1401
+ cm.setSelection(selectionStart, selectionEnd);
1378
1402
  }
1379
- cm.setSelection(selectionStart, selectionEnd);
1380
1403
  updateMark(cm, vim, '<',
1381
1404
  cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
1382
1405
  : selectionEnd);
@@ -1392,11 +1415,13 @@
1392
1415
  if (operator) {
1393
1416
  var inverted = false;
1394
1417
  vim.lastMotion = null;
1418
+ var lastSelection = vim.lastSelection;
1395
1419
  operatorArgs.repeat = repeat; // Indent in visual mode needs this.
1396
1420
  if (vim.visualMode) {
1397
1421
  curStart = selectionStart;
1398
1422
  curEnd = selectionEnd;
1399
1423
  motionArgs.inclusive = true;
1424
+ operatorArgs.shouldMoveCursor = false;
1400
1425
  }
1401
1426
  // Swap start and end if motion was backward.
1402
1427
  if (curEnd && cursorIsBefore(curEnd, curStart)) {
@@ -1417,6 +1442,24 @@
1417
1442
  curEnd.line = curStart.line + operatorArgs.selOffset.line;
1418
1443
  if (operatorArgs.selOffset.line) {curEnd.ch = operatorArgs.selOffset.ch; }
1419
1444
  else { curEnd.ch = curStart.ch + operatorArgs.selOffset.ch; }
1445
+ // In case of blockwise visual
1446
+ if (lastSelection && lastSelection.visualBlock) {
1447
+ var block = lastSelection.visualBlock;
1448
+ var width = block.width;
1449
+ var height = block.height;
1450
+ curEnd = Pos(curStart.line + height, curStart.ch + width);
1451
+ // selectBlock creates a 'proper' rectangular block.
1452
+ // We do not want that in all cases, so we manually set selections.
1453
+ var selections = [];
1454
+ for (var i = curStart.line; i < curEnd.line; i++) {
1455
+ var anchor = Pos(i, curStart.ch);
1456
+ var head = Pos(i, curEnd.ch);
1457
+ var range = {anchor: anchor, head: head};
1458
+ selections.push(range);
1459
+ }
1460
+ cm.setSelections(selections);
1461
+ var blockSelected = true;
1462
+ }
1420
1463
  } else if (vim.visualMode) {
1421
1464
  var selOffset = Pos();
1422
1465
  selOffset.line = curEnd.line - curStart.line;
@@ -1437,6 +1480,9 @@
1437
1480
  operatorArgs.registerName = registerName;
1438
1481
  // Keep track of linewise as it affects how paste and change behave.
1439
1482
  operatorArgs.linewise = linewise;
1483
+ if (!vim.visualBlock && !blockSelected) {
1484
+ cm.setSelection(curStart, curEnd);
1485
+ }
1440
1486
  operators[operator](cm, operatorArgs, vim, curStart,
1441
1487
  curEnd, curOriginal);
1442
1488
  if (vim.visualMode) {
@@ -1499,15 +1545,19 @@
1499
1545
  }
1500
1546
  return null;
1501
1547
  },
1502
- moveToOtherHighlightedEnd: function(cm) {
1503
- var curEnd = copyCursor(cm.getCursor('head'));
1504
- var curStart = copyCursor(cm.getCursor('anchor'));
1505
- if (cursorIsBefore(curStart, curEnd)) {
1506
- curEnd.ch += 1;
1507
- } else if (cursorIsBefore(curEnd, curStart)) {
1508
- curStart.ch -= 1;
1509
- }
1510
- return ([curEnd,curStart]);
1548
+ moveToOtherHighlightedEnd: function(cm, motionArgs, vim) {
1549
+ var ranges = cm.listSelections();
1550
+ var curEnd = cm.getCursor('head');
1551
+ var curStart = ranges[0].anchor;
1552
+ var curIndex = cursorEqual(ranges[0].head, curEnd) ? ranges.length-1 : 0;
1553
+ if (motionArgs.sameLine && vim.visualBlock) {
1554
+ curStart = Pos(curEnd.line, ranges[curIndex].anchor.ch);
1555
+ curEnd = Pos(ranges[curIndex].head.line, curEnd.ch);
1556
+ } else {
1557
+ curStart = ranges[curIndex].anchor;
1558
+ }
1559
+ cm.setCursor(curEnd);
1560
+ return ([curEnd, curStart]);
1511
1561
  },
1512
1562
  jumpToMark: function(cm, motionArgs, vim) {
1513
1563
  var best = cm.getCursor();
@@ -1804,17 +1854,41 @@
1804
1854
  };
1805
1855
 
1806
1856
  var operators = {
1807
- change: function(cm, operatorArgs, _vim, curStart, curEnd) {
1857
+ change: function(cm, operatorArgs, vim) {
1858
+ var selections = cm.listSelections();
1859
+ var start = selections[0], end = selections[selections.length-1];
1860
+ var curStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
1861
+ var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
1862
+ var text = cm.getSelection();
1863
+ var replacement = new Array(selections.length).join('1').split('1');
1864
+ // save the selectionEnd mark
1865
+ var selectionEnd = vim.marks['>'] ? vim.marks['>'].find() : cm.getCursor('head');
1808
1866
  vimGlobalState.registerController.pushText(
1809
- operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd),
1867
+ operatorArgs.registerName, 'change', text,
1810
1868
  operatorArgs.linewise);
1811
1869
  if (operatorArgs.linewise) {
1812
- // Push the next line back down, if there is a next line.
1813
- var replacement = curEnd.line > cm.lastLine() ? '' : '\n';
1814
- cm.replaceRange(replacement, curStart, curEnd);
1815
- cm.indentLine(curStart.line, 'smart');
1816
- // null ch so setCursor moves to end of line.
1817
- curStart.ch = null;
1870
+ // 'C' in visual block extends the block till eol for all lines
1871
+ if (vim.visualBlock){
1872
+ var startLine = curStart.line;
1873
+ while (startLine <= curEnd.line) {
1874
+ var endCh = lineLength(cm, startLine);
1875
+ var head = Pos(startLine, endCh);
1876
+ var anchor = Pos(startLine, curStart.ch);
1877
+ startLine++;
1878
+ cm.replaceRange('', anchor, head);
1879
+ }
1880
+ } else {
1881
+ // Push the next line back down, if there is a next line.
1882
+ replacement = '\n';
1883
+ if (curEnd.line == curStart.line && curEnd.line == cm.lastLine()) {
1884
+ replacement = '';
1885
+ }
1886
+ cm.replaceRange(replacement, curStart, curEnd);
1887
+ cm.indentLine(curStart.line, 'smart');
1888
+ // null ch so setCursor moves to end of line.
1889
+ curStart.ch = null;
1890
+ cm.setCursor(curStart);
1891
+ }
1818
1892
  } else {
1819
1893
  // Exclude trailing whitespace if the range is not all whitespace.
1820
1894
  var text = cm.getRange(curStart, curEnd);
@@ -1824,24 +1898,60 @@
1824
1898
  curEnd = offsetCursor(curEnd, 0, - match[0].length);
1825
1899
  }
1826
1900
  }
1827
- cm.replaceRange('', curStart, curEnd);
1901
+ if (vim.visualBlock) {
1902
+ cm.replaceSelections(replacement);
1903
+ } else {
1904
+ cm.setCursor(curStart);
1905
+ cm.replaceRange('', curStart, curEnd);
1906
+ }
1828
1907
  }
1908
+ vim.marks['>'] = cm.setBookmark(selectionEnd);
1829
1909
  actions.enterInsertMode(cm, {}, cm.state.vim);
1830
- cm.setCursor(curStart);
1831
1910
  },
1832
1911
  // delete is a javascript keyword.
1833
- 'delete': function(cm, operatorArgs, _vim, curStart, curEnd) {
1912
+ 'delete': function(cm, operatorArgs, vim) {
1913
+ var selections = cm.listSelections();
1914
+ var start = selections[0], end = selections[selections.length-1];
1915
+ var curStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
1916
+ var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
1917
+ // Save the '>' mark before cm.replaceRange clears it.
1918
+ var selectionEnd, selectionStart;
1919
+ if (vim.visualMode) {
1920
+ selectionEnd = vim.marks['>'].find();
1921
+ selectionStart = vim.marks['<'].find();
1922
+ } else if (vim.lastSelection) {
1923
+ selectionEnd = vim.lastSelection.curStartMark.find();
1924
+ selectionStart = vim.lastSelection.curEndMark.find();
1925
+ }
1926
+ var text = cm.getSelection();
1927
+ vimGlobalState.registerController.pushText(
1928
+ operatorArgs.registerName, 'delete', text,
1929
+ operatorArgs.linewise);
1930
+ var replacement = new Array(selections.length).join('1').split('1');
1834
1931
  // If the ending line is past the last line, inclusive, instead of
1835
1932
  // including the trailing \n, include the \n before the starting line
1836
1933
  if (operatorArgs.linewise &&
1837
- curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) {
1934
+ curEnd.line == cm.lastLine() && curStart.line == curEnd.line) {
1935
+ var tmp = copyCursor(curEnd);
1838
1936
  curStart.line--;
1839
1937
  curStart.ch = lineLength(cm, curStart.line);
1938
+ curEnd = tmp;
1939
+ cm.replaceRange('', curStart, curEnd);
1940
+ } else {
1941
+ cm.replaceSelections(replacement);
1942
+ }
1943
+ // restore the saved bookmark
1944
+ if (selectionEnd) {
1945
+ var curStartMark = cm.setBookmark(selectionStart);
1946
+ var curEndMark = cm.setBookmark(selectionEnd);
1947
+ if (vim.visualMode) {
1948
+ vim.marks['<'] = curStartMark;
1949
+ vim.marks['>'] = curEndMark;
1950
+ } else {
1951
+ vim.lastSelection.curStartMark = curStartMark;
1952
+ vim.lastSelection.curEndMark = curEndMark;
1953
+ }
1840
1954
  }
1841
- vimGlobalState.registerController.pushText(
1842
- operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd),
1843
- operatorArgs.linewise);
1844
- cm.replaceRange('', curStart, curEnd);
1845
1955
  if (operatorArgs.linewise) {
1846
1956
  cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1847
1957
  } else {
@@ -1868,23 +1978,32 @@
1868
1978
  cm.setCursor(curStart);
1869
1979
  cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1870
1980
  },
1871
- swapcase: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
1872
- var toSwap = cm.getRange(curStart, curEnd);
1873
- var swapped = '';
1874
- for (var i = 0; i < toSwap.length; i++) {
1875
- var character = toSwap.charAt(i);
1876
- swapped += isUpperCase(character) ? character.toLowerCase() :
1877
- character.toUpperCase();
1878
- }
1879
- cm.replaceRange(swapped, curStart, curEnd);
1981
+ swapcase: function(cm, operatorArgs, _vim, _curStart, _curEnd, _curOriginal) {
1982
+ var selections = cm.getSelections();
1983
+ var ranges = cm.listSelections();
1984
+ var swapped = [];
1985
+ for (var j = 0; j < selections.length; j++) {
1986
+ var toSwap = selections[j];
1987
+ var text = '';
1988
+ for (var i = 0; i < toSwap.length; i++) {
1989
+ var character = toSwap.charAt(i);
1990
+ text += isUpperCase(character) ? character.toLowerCase() :
1991
+ character.toUpperCase();
1992
+ }
1993
+ swapped.push(text);
1994
+ }
1995
+ cm.replaceSelections(swapped);
1996
+ var curStart = ranges[0].anchor;
1997
+ var curEnd = ranges[0].head;
1880
1998
  if (!operatorArgs.shouldMoveCursor) {
1881
- cm.setCursor(curOriginal);
1999
+ cm.setCursor(cursorIsBefore(curStart, curEnd) ? curStart : curEnd);
1882
2000
  }
1883
2001
  },
1884
- yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
2002
+ yank: function(cm, operatorArgs, _vim, _curStart, _curEnd, curOriginal) {
2003
+ var text = cm.getSelection();
1885
2004
  vimGlobalState.registerController.pushText(
1886
2005
  operatorArgs.registerName, 'yank',
1887
- cm.getRange(curStart, curEnd), operatorArgs.linewise);
2006
+ text, operatorArgs.linewise);
1888
2007
  cm.setCursor(curOriginal);
1889
2008
  }
1890
2009
  };
@@ -2015,6 +2134,7 @@
2015
2134
  var repeat = actionArgs.repeat;
2016
2135
  var curStart = cm.getCursor();
2017
2136
  var curEnd;
2137
+ var selections = cm.listSelections();
2018
2138
  // TODO: The repeat should actually select number of characters/lines
2019
2139
  // equal to the repeat times the size of the previous visual
2020
2140
  // operation.
@@ -2022,6 +2142,7 @@
2022
2142
  cm.on('mousedown', exitVisualMode);
2023
2143
  vim.visualMode = true;
2024
2144
  vim.visualLine = !!actionArgs.linewise;
2145
+ vim.visualBlock = !!actionArgs.blockwise;
2025
2146
  if (vim.visualLine) {
2026
2147
  curStart.ch = 0;
2027
2148
  curEnd = clipCursorToContent(
@@ -2037,24 +2158,57 @@
2037
2158
  } else {
2038
2159
  curStart = cm.getCursor('anchor');
2039
2160
  curEnd = cm.getCursor('head');
2040
- if (!vim.visualLine && actionArgs.linewise) {
2041
- // Shift-V pressed in characterwise visual mode. Switch to linewise
2042
- // visual mode instead of exiting visual mode.
2043
- vim.visualLine = true;
2044
- curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 :
2161
+ if (vim.visualLine) {
2162
+ if (actionArgs.blockwise) {
2163
+ // This means Ctrl-V pressed in linewise visual
2164
+ vim.visualBlock = true;
2165
+ selectBlock(cm, curEnd);
2166
+ CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'blockwise'});
2167
+ } else if (!actionArgs.linewise) {
2168
+ // v pressed in linewise, switch to characterwise visual mode
2169
+ CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual'});
2170
+ } else {
2171
+ exitVisualMode(cm);
2172
+ }
2173
+ vim.visualLine = false;
2174
+ } else if (vim.visualBlock) {
2175
+ if (actionArgs.linewise) {
2176
+ // Shift-V pressed in blockwise visual mode
2177
+ vim.visualLine = true;
2178
+ curStart = Pos(selections[0].anchor.line, 0);
2179
+ curEnd = Pos(selections[selections.length-1].anchor.line, lineLength(cm, selections[selections.length-1].anchor.line));
2180
+ cm.setSelection(curStart, curEnd);
2181
+ CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'linewise'});
2182
+ } else if (!actionArgs.blockwise) {
2183
+ // v pressed in blockwise mode, Switch to characterwise
2184
+ if (curEnd != selections[0].head) {
2185
+ curStart = selections[0].anchor;
2186
+ } else {
2187
+ curStart = selections[selections.length-1].anchor;
2188
+ }
2189
+ cm.setSelection(curStart, curEnd);
2190
+ CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual'});
2191
+ } else {
2192
+ exitVisualMode(cm);
2193
+ }
2194
+ vim.visualBlock = false;
2195
+ } else if (actionArgs.linewise) {
2196
+ // Shift-V pressed in characterwise visual mode. Switch to linewise
2197
+ // visual mode instead of exiting visual mode.
2198
+ vim.visualLine = true;
2199
+ curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 :
2045
2200
  lineLength(cm, curStart.line);
2046
- curEnd.ch = cursorIsBefore(curStart, curEnd) ?
2201
+ curEnd.ch = cursorIsBefore(curStart, curEnd) ?
2047
2202
  lineLength(cm, curEnd.line) : 0;
2048
- cm.setSelection(curStart, curEnd);
2049
- CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"});
2050
- } else if (vim.visualLine && !actionArgs.linewise) {
2051
- // v pressed in linewise visual mode. Switch to characterwise visual
2052
- // mode instead of exiting visual mode.
2053
- vim.visualLine = false;
2054
- CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
2055
- } else {
2056
- exitVisualMode(cm);
2057
- }
2203
+ cm.setSelection(curStart, curEnd);
2204
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"});
2205
+ } else if (actionArgs.blockwise) {
2206
+ vim.visualBlock = true;
2207
+ selectBlock(cm, curEnd);
2208
+ CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'blockwise'});
2209
+ } else {
2210
+ exitVisualMode(cm);
2211
+ }
2058
2212
  }
2059
2213
  updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart
2060
2214
  : curEnd);
@@ -2062,27 +2216,40 @@
2062
2216
  : curStart);
2063
2217
  },
2064
2218
  reselectLastSelection: function(cm, _actionArgs, vim) {
2219
+ var curStart = vim.marks['<'].find();
2220
+ var curEnd = vim.marks['>'].find();
2065
2221
  var lastSelection = vim.lastSelection;
2066
2222
  if (lastSelection) {
2067
- var curStart = lastSelection.curStartMark.find();
2068
- var curEnd = lastSelection.curEndMark.find();
2069
- cm.setSelection(curStart, curEnd);
2223
+ // Set the selections as per last selection
2224
+ var selectionStart = lastSelection.curStartMark.find();
2225
+ var selectionEnd = lastSelection.curEndMark.find();
2226
+ var blockwise = lastSelection.visualBlock;
2227
+ // update last selection
2228
+ updateLastSelection(cm, vim, curStart, curEnd);
2229
+ if (blockwise) {
2230
+ cm.setCursor(selectionStart);
2231
+ selectionStart = selectBlock(cm, selectionEnd);
2232
+ } else {
2233
+ cm.setSelection(selectionStart, selectionEnd);
2234
+ selectionStart = cm.getCursor('anchor');
2235
+ selectionEnd = cm.getCursor('head');
2236
+ }
2070
2237
  if (vim.visualMode) {
2071
- updateLastSelection(cm, vim);
2072
- var selectionStart = cm.getCursor('anchor');
2073
- var selectionEnd = cm.getCursor('head');
2074
2238
  updateMark(cm, vim, '<', cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
2075
- : selectionEnd);
2239
+ : selectionEnd);
2076
2240
  updateMark(cm, vim, '>', cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd
2077
- : selectionStart);
2241
+ : selectionStart);
2078
2242
  }
2243
+ // Last selection is updated now
2244
+ vim.visualMode = true;
2079
2245
  if (lastSelection.visualLine) {
2080
- vim.visualMode = true;
2081
2246
  vim.visualLine = true;
2082
- }
2083
- else {
2084
- vim.visualMode = true;
2247
+ vim.visualBlock = false;
2248
+ } else if (lastSelection.visualBlock) {
2085
2249
  vim.visualLine = false;
2250
+ vim.visualBlock = true;
2251
+ } else {
2252
+ vim.visualBlock = vim.visualLine = false;
2086
2253
  }
2087
2254
  CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
2088
2255
  }
@@ -2224,6 +2391,9 @@
2224
2391
  }
2225
2392
  }
2226
2393
  cm.setCursor(curPosFinal);
2394
+ if (vim.visualMode) {
2395
+ exitVisualMode(cm);
2396
+ }
2227
2397
  },
2228
2398
  undo: function(cm, actionArgs) {
2229
2399
  cm.operation(function() {
@@ -2246,10 +2416,11 @@
2246
2416
  var curStart = cm.getCursor();
2247
2417
  var replaceTo;
2248
2418
  var curEnd;
2249
- if (vim.visualMode){
2250
- curStart=cm.getCursor('start');
2251
- curEnd=cm.getCursor('end');
2252
- }else{
2419
+ var selections = cm.listSelections();
2420
+ if (vim.visualMode) {
2421
+ curStart = cm.getCursor('start');
2422
+ curEnd = cm.getCursor('end');
2423
+ } else {
2253
2424
  var line = cm.getLine(curStart.line);
2254
2425
  replaceTo = curStart.ch + actionArgs.repeat;
2255
2426
  if (replaceTo > line.length) {
@@ -2257,19 +2428,29 @@
2257
2428
  }
2258
2429
  curEnd = Pos(curStart.line, replaceTo);
2259
2430
  }
2260
- if (replaceWith=='\n'){
2431
+ if (replaceWith=='\n') {
2261
2432
  if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
2262
2433
  // special case, where vim help says to replace by just one line-break
2263
2434
  (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
2264
- }else {
2265
- var replaceWithStr=cm.getRange(curStart, curEnd);
2435
+ } else {
2436
+ var replaceWithStr = cm.getRange(curStart, curEnd);
2266
2437
  //replace all characters in range by selected, but keep linebreaks
2267
- replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith);
2268
- cm.replaceRange(replaceWithStr, curStart, curEnd);
2269
- if (vim.visualMode){
2438
+ replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith);
2439
+ if (vim.visualBlock) {
2440
+ // Tabs are split in visua block before replacing
2441
+ var spaces = new Array(cm.options.tabSize+1).join(' ');
2442
+ replaceWithStr = cm.getSelection();
2443
+ replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n');
2444
+ cm.replaceSelections(replaceWithStr);
2445
+ } else {
2446
+ cm.replaceRange(replaceWithStr, curStart, curEnd);
2447
+ }
2448
+ if (vim.visualMode) {
2449
+ curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ?
2450
+ selections[0].anchor : selections[0].head;
2270
2451
  cm.setCursor(curStart);
2271
2452
  exitVisualMode(cm);
2272
- }else{
2453
+ } else {
2273
2454
  cm.setCursor(offsetCursor(curEnd, 0, -1));
2274
2455
  }
2275
2456
  }
@@ -2314,17 +2495,26 @@
2314
2495
  repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
2315
2496
  },
2316
2497
  changeCase: function(cm, actionArgs, vim) {
2317
- var selectedAreaRange = getSelectedAreaRange(cm, vim);
2318
- var selectionStart = selectedAreaRange[0];
2319
- var selectionEnd = selectedAreaRange[1];
2498
+ var selectionStart = getSelectedAreaRange(cm, vim)[0];
2499
+ var text = cm.getSelection();
2500
+ var lastSelectionCurEnd;
2501
+ var blockSelection;
2502
+ if (vim.lastSelection) {
2320
2503
  // save the curEnd marker to avoid its removal due to cm.replaceRange
2321
- var lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
2504
+ lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
2505
+ blockSelection = vim.lastSelection.visualBlock;
2506
+ }
2322
2507
  var toLower = actionArgs.toLower;
2323
- var text = cm.getRange(selectionStart, selectionEnd);
2324
- cm.replaceRange(toLower ? text.toLowerCase() : text.toUpperCase(), selectionStart, selectionEnd);
2508
+ text = toLower ? text.toLowerCase() : text.toUpperCase();
2509
+ cm.replaceSelections(vim.visualBlock || blockSelection ? text.split('\n') : [text]);
2325
2510
  // restore the last selection curEnd marker
2326
- vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd);
2511
+ if (lastSelectionCurEnd) {
2512
+ vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd);
2513
+ }
2327
2514
  cm.setCursor(selectionStart);
2515
+ if (vim.visualMode) {
2516
+ exitVisualMode(cm);
2517
+ }
2328
2518
  }
2329
2519
  };
2330
2520
 
@@ -2407,45 +2597,161 @@
2407
2597
  function escapeRegex(s) {
2408
2598
  return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
2409
2599
  }
2600
+ // This functions selects a rectangular block
2601
+ // of text with selectionEnd as any of its corner
2602
+ // Height of block:
2603
+ // Difference in selectionEnd.line and first/last selection.line
2604
+ // Width of the block:
2605
+ // Distance between selectionEnd.ch and any(first considered here) selection.ch
2606
+ function selectBlock(cm, selectionEnd) {
2607
+ var selections = [], ranges = cm.listSelections();
2608
+ var firstRange = ranges[0].anchor, lastRange = ranges[ranges.length-1].anchor;
2609
+ var start, end, selectionStart;
2610
+ var curEnd = cm.getCursor('head');
2611
+ var primIndex = getIndex(ranges, curEnd);
2612
+ // sets to true when selectionEnd already lies inside the existing selections
2613
+ var contains = getIndex(ranges, selectionEnd) < 0 ? false : true;
2614
+ selectionEnd = cm.clipPos(selectionEnd);
2615
+ // difference in distance of selectionEnd from each end of the block.
2616
+ var near = Math.abs(firstRange.line - selectionEnd.line) - Math.abs(lastRange.line - selectionEnd.line);
2617
+ if (near > 0) {
2618
+ end = selectionEnd.line;
2619
+ start = firstRange.line;
2620
+ if (lastRange.line == selectionEnd.line && contains) {
2621
+ start = end;
2622
+ }
2623
+ } else if (near < 0) {
2624
+ start = selectionEnd.line;
2625
+ end = lastRange.line;
2626
+ if (firstRange.line == selectionEnd.line && contains) {
2627
+ end = start;
2628
+ }
2629
+ } else {
2630
+ // Case where selectionEnd line is halfway between the 2 ends.
2631
+ // We remove the primary selection in this case
2632
+ if (primIndex == 0) {
2633
+ start = selectionEnd.line;
2634
+ end = lastRange.line;
2635
+ } else {
2636
+ start = firstRange.line;
2637
+ end = selectionEnd.line;
2638
+ }
2639
+ }
2640
+ if (start > end) {
2641
+ var tmp = start;
2642
+ start = end;
2643
+ end = tmp;
2644
+ }
2645
+ selectionStart = (near > 0) ? firstRange : lastRange;
2646
+ while (start <= end) {
2647
+ var anchor = {line: start, ch: selectionStart.ch};
2648
+ var head = {line: start, ch: selectionEnd.ch};
2649
+ // Shift the anchor right or left
2650
+ // as each selection crosses itself.
2651
+ if ((anchor.ch < curEnd.ch) && ((head.ch == anchor.ch) || (anchor.ch - head.ch == 1))) {
2652
+ anchor.ch++;
2653
+ head.ch--;
2654
+ } else if ((anchor.ch > curEnd.ch) && ((head.ch == anchor.ch) || (anchor.ch - head.ch == -1))) {
2655
+ anchor.ch--;
2656
+ head.ch++;
2657
+ }
2658
+ var range = {anchor: anchor, head: head};
2659
+ selections.push(range);
2660
+ if (cursorEqual(head, selectionEnd)) {
2661
+ primIndex = selections.indexOf(range);
2662
+ }
2663
+ start++;
2664
+ }
2665
+ // Update selectionEnd and selectionStart
2666
+ // after selection crossing
2667
+ selectionEnd.ch = selections[0].head.ch;
2668
+ selectionStart.ch = selections[0].anchor.ch;
2669
+ cm.setSelections(selections, primIndex);
2670
+ return selectionStart;
2671
+ }
2672
+ function getIndex(ranges, head) {
2673
+ for (var i = 0; i < ranges.length; i++) {
2674
+ if (cursorEqual(ranges[i].head, head)) {
2675
+ return i;
2676
+ }
2677
+ }
2678
+ return -1;
2679
+ }
2410
2680
  function getSelectedAreaRange(cm, vim) {
2411
- var selectionStart = cm.getCursor('anchor');
2412
- var selectionEnd = cm.getCursor('head');
2413
2681
  var lastSelection = vim.lastSelection;
2414
- if (!vim.visualMode) {
2415
- var lastSelectionCurStart = vim.lastSelection.curStartMark.find();
2416
- var lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
2417
- var line = lastSelectionCurEnd.line - lastSelectionCurStart.line;
2418
- var ch = line ? lastSelectionCurEnd.ch : lastSelectionCurEnd.ch - lastSelectionCurStart.ch;
2419
- selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch};
2420
- if (lastSelection.visualLine) {
2421
- return [{line: selectionStart.line, ch: 0}, {line: selectionEnd.line, ch: lineLength(cm, selectionEnd.line)}];
2682
+ var getCurrentSelectedAreaRange = function() {
2683
+ var selections = cm.listSelections();
2684
+ var start = selections[0];
2685
+ var end = selections[selections.length-1];
2686
+ var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
2687
+ var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
2688
+ return [selectionStart, selectionEnd];
2689
+ };
2690
+ var getLastSelectedAreaRange = function() {
2691
+ var selectionStart = cm.getCursor();
2692
+ var selectionEnd = cm.getCursor();
2693
+ var block = lastSelection.visualBlock;
2694
+ if (block) {
2695
+ var width = block.width;
2696
+ var height = block.height;
2697
+ selectionEnd = Pos(selectionStart.line + height, selectionStart.ch + width);
2698
+ var selections = [];
2699
+ // selectBlock creates a 'proper' rectangular block.
2700
+ // We do not want that in all cases, so we manually set selections.
2701
+ for (var i = selectionStart.line; i < selectionEnd.line; i++) {
2702
+ var anchor = Pos(i, selectionStart.ch);
2703
+ var head = Pos(i, selectionEnd.ch);
2704
+ var range = {anchor: anchor, head: head};
2705
+ selections.push(range);
2706
+ }
2707
+ cm.setSelections(selections);
2708
+ } else {
2709
+ var start = lastSelection.curStartMark.find();
2710
+ var end = lastSelection.curEndMark.find();
2711
+ var line = end.line - start.line;
2712
+ var ch = end.ch - start.ch;
2713
+ selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch};
2714
+ if (lastSelection.visualLine) {
2715
+ selectionStart = Pos(selectionStart.line, 0);
2716
+ selectionEnd = Pos(selectionEnd.line, lineLength(cm, selectionEnd.line));
2717
+ }
2718
+ cm.setSelection(selectionStart, selectionEnd);
2422
2719
  }
2720
+ return [selectionStart, selectionEnd];
2721
+ };
2722
+ if (!vim.visualMode) {
2723
+ // In case of replaying the action.
2724
+ return getLastSelectedAreaRange();
2423
2725
  } else {
2424
- if (cursorIsBefore(selectionEnd, selectionStart)) {
2425
- var tmp = selectionStart;
2426
- selectionStart = selectionEnd;
2427
- selectionEnd = tmp;
2428
- }
2429
- exitVisualMode(cm);
2726
+ return getCurrentSelectedAreaRange();
2430
2727
  }
2431
- return [selectionStart, selectionEnd];
2432
2728
  }
2433
- function updateLastSelection(cm, vim) {
2434
- // We need the vim mark '<' to get the selection in case of yank and put
2435
- var selectionStart = vim.marks['<'].find() || cm.getCursor('anchor');
2436
- var selectionEnd = vim.marks['>'].find() ||cm.getCursor('head');
2437
- // To accommodate the effect lastPastedText in the last selection
2729
+ function updateLastSelection(cm, vim, selectionStart, selectionEnd) {
2730
+ if (!selectionStart || !selectionEnd) {
2731
+ selectionStart = vim.marks['<'].find() || cm.getCursor('anchor');
2732
+ selectionEnd = vim.marks['>'].find() || cm.getCursor('head');
2733
+ }
2734
+ // To accommodate the effect of lastPastedText in the last selection
2438
2735
  if (vim.lastPastedText) {
2439
- selectionEnd = cm.posFromIndex(cm.indexFromPos(selectionStart) + vim.lastPastedText.length-1);
2736
+ selectionEnd = cm.posFromIndex(cm.indexFromPos(selectionStart) + vim.lastPastedText.length);
2440
2737
  vim.lastPastedText = null;
2441
2738
  }
2739
+ var ranges = cm.listSelections();
2740
+ // This check ensures to set the cursor
2741
+ // position where we left off in previous selection
2742
+ var swap = getIndex(ranges, selectionStart) > -1;
2743
+ if (vim.visualBlock) {
2744
+ var height = Math.abs(selectionStart.line - selectionEnd.line)+1;
2745
+ var width = Math.abs(selectionStart.ch - selectionEnd.ch);
2746
+ var block = {height: height, width: width};
2747
+ }
2442
2748
  // can't use selection state here because yank has already reset its cursor
2443
2749
  // Also, Bookmarks make the visual selections robust to edit operations
2444
- vim.lastSelection = {'curStartMark': cm.setBookmark(selectionStart), 'curEndMark': cm.setBookmark(selectionEnd), 'visualMode': vim.visualMode, 'visualLine': vim.visualLine};
2445
- if (cursorIsBefore(selectionEnd, selectionStart)) {
2446
- vim.lastSelection.curStartMark = cm.setBookmark(selectionEnd);
2447
- vim.lastSelection.curEndMark = cm.setBookmark(selectionStart);
2448
- }
2750
+ vim.lastSelection = {'curStartMark': cm.setBookmark(swap ? selectionEnd : selectionStart),
2751
+ 'curEndMark': cm.setBookmark(swap ? selectionStart : selectionEnd),
2752
+ 'visualMode': vim.visualMode,
2753
+ 'visualLine': vim.visualLine,
2754
+ 'visualBlock': block};
2449
2755
  }
2450
2756
 
2451
2757
  function exitVisualMode(cm) {
@@ -2456,6 +2762,7 @@
2456
2762
  updateLastSelection(cm, vim);
2457
2763
  vim.visualMode = false;
2458
2764
  vim.visualLine = false;
2765
+ vim.visualBlock = false;
2459
2766
  if (!cursorEqual(selectionStart, selectionEnd)) {
2460
2767
  // Clear the selection and set the cursor only if the selection has not
2461
2768
  // already been cleared. Otherwise we risk moving the cursor somewhere