codemirror-rails 4.3 → 4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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