codemirror-rails 4.4 → 4.5

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.
@@ -37,7 +37,7 @@
37
37
  constructor: DiffView,
38
38
  init: function(pane, orig, options) {
39
39
  this.edit = this.mv.edit;
40
- this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: true}, copyObj(options)));
40
+ this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));
41
41
 
42
42
  this.diff = getDiff(asString(orig), asString(options.value));
43
43
  this.diffOutOfDate = false;
@@ -71,7 +71,7 @@
71
71
  function update(mode) {
72
72
  if (mode == "full") {
73
73
  if (dv.svg) clear(dv.svg);
74
- clear(dv.copyButtons);
74
+ if (dv.copyButtons) clear(dv.copyButtons);
75
75
  clearMarks(dv.edit, edit.marked, dv.classes);
76
76
  clearMarks(dv.orig, orig.marked, dv.classes);
77
77
  edit.from = edit.to = orig.from = orig.to = 0;
@@ -257,7 +257,7 @@
257
257
  var w = dv.gap.offsetWidth;
258
258
  attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight);
259
259
  }
260
- clear(dv.copyButtons);
260
+ if (dv.copyButtons) clear(dv.copyButtons);
261
261
 
262
262
  var flip = dv.type == "left";
263
263
  var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
@@ -279,17 +279,30 @@
279
279
  "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z",
280
280
  "class", dv.classes.connect);
281
281
  }
282
- var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
283
- "CodeMirror-merge-copy"));
284
- copy.title = "Revert chunk";
285
- copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig};
286
- copy.style.top = top + "px";
282
+ if (dv.copyButtons) {
283
+ var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
284
+ "CodeMirror-merge-copy"));
285
+ var editOriginals = dv.mv.options.allowEditingOriginals;
286
+ copy.title = editOriginals ? "Push to left" : "Revert chunk";
287
+ copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig};
288
+ copy.style.top = top + "px";
289
+
290
+ if (editOriginals) {
291
+ var topReverse = dv.orig.heightAtLine(topEdit, "local") - sTopEdit;
292
+ var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
293
+ "CodeMirror-merge-copy-reverse"));
294
+ copyReverse.title = "Push to right";
295
+ copyReverse.chunk = {topEdit: topOrig, botEdit: botOrig, topOrig: topEdit, botOrig: botEdit};
296
+ copyReverse.style.top = topReverse + "px";
297
+ dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px";
298
+ }
299
+ }
287
300
  });
288
301
  }
289
302
 
290
- function copyChunk(dv, chunk) {
303
+ function copyChunk(dv, to, from, chunk) {
291
304
  if (dv.diffOutOfDate) return;
292
- dv.edit.replaceRange(dv.orig.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)),
305
+ to.replaceRange(from.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)),
293
306
  Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0));
294
307
  }
295
308
 
@@ -298,6 +311,7 @@
298
311
  var MergeView = CodeMirror.MergeView = function(node, options) {
299
312
  if (!(this instanceof MergeView)) return new MergeView(node, options);
300
313
 
314
+ this.options = options;
301
315
  var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight;
302
316
  var hasLeft = origLeft != null, hasRight = origRight != null;
303
317
  var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0);
@@ -323,6 +337,7 @@
323
337
  (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost";
324
338
 
325
339
  wrap.push(elt("div", null, null, "height: 0; clear: both;"));
340
+
326
341
  var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane"));
327
342
  this.edit = CodeMirror(editPane, copyObj(options));
328
343
 
@@ -345,12 +360,20 @@
345
360
  lock.title = "Toggle locked scrolling";
346
361
  var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap");
347
362
  CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); });
348
- dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
349
- CodeMirror.on(dv.copyButtons, "click", function(e) {
350
- var node = e.target || e.srcElement;
351
- if (node.chunk) copyChunk(dv, node.chunk);
352
- });
353
- var gapElts = [dv.copyButtons, lockWrap];
363
+ var gapElts = [lockWrap];
364
+ if (dv.mv.options.revertButtons !== false) {
365
+ dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
366
+ CodeMirror.on(dv.copyButtons, "click", function(e) {
367
+ var node = e.target || e.srcElement;
368
+ if (!node.chunk) return;
369
+ if (node.className == "CodeMirror-merge-copy-reverse") {
370
+ copyChunk(dv, dv.orig, dv.edit, node.chunk);
371
+ return;
372
+ }
373
+ copyChunk(dv, dv.edit, dv.orig, node.chunk);
374
+ });
375
+ gapElts.unshift(dv.copyButtons);
376
+ }
354
377
  var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
355
378
  if (svg && !svg.createSVGRect) svg = null;
356
379
  dv.svg = svg;
@@ -71,7 +71,7 @@
71
71
  return query;
72
72
  }
73
73
  var queryDialog =
74
- 'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
74
+ 'Search: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
75
75
  function doSearch(cm, rev) {
76
76
  var state = getSearchState(cm);
77
77
  if (state.query) return findNext(cm, rev);
@@ -106,8 +106,8 @@
106
106
  });}
107
107
 
108
108
  var replaceQueryDialog =
109
- 'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
110
- var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
109
+ 'Replace: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
110
+ var replacementQueryDialog = 'With: <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
111
111
  var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
112
112
  function replace(cm, all) {
113
113
  if (cm.getOption("readOnly")) return;
@@ -17,7 +17,8 @@
17
17
  var map = CodeMirror.keyMap.sublime = {fallthrough: "default"};
18
18
  var cmds = CodeMirror.commands;
19
19
  var Pos = CodeMirror.Pos;
20
- var ctrl = CodeMirror.keyMap["default"] == CodeMirror.keyMap.pcDefault ? "Ctrl-" : "Cmd-";
20
+ var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
21
+ var ctrl = mac ? "Cmd-" : "Ctrl-";
21
22
 
22
23
  // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that.
23
24
  function findPosSubword(doc, start, dir) {
@@ -186,7 +187,9 @@
186
187
  });
187
188
  };
188
189
 
189
- cmds[map["Shift-" + ctrl + "Up"] = "swapLineUp"] = function(cm) {
190
+ var swapLineCombo = mac ? "Cmd-Ctrl-" : "Shift-Ctrl-";
191
+
192
+ cmds[map[swapLineCombo + "Up"] = "swapLineUp"] = function(cm) {
190
193
  var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = [];
191
194
  for (var i = 0; i < ranges.length; i++) {
192
195
  var range = ranges[i], from = range.from().line - 1, to = range.to().line;
@@ -212,7 +215,7 @@
212
215
  });
213
216
  };
214
217
 
215
- cmds[map["Shift-" + ctrl + "Down"] = "swapLineDown"] = function(cm) {
218
+ cmds[map[swapLineCombo + "Down"] = "swapLineDown"] = function(cm) {
216
219
  var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;
217
220
  for (var i = ranges.length - 1; i >= 0; i--) {
218
221
  var range = ranges[i], from = range.to().line + 1, to = range.from().line;
@@ -616,7 +616,9 @@
616
616
  visualLine: false,
617
617
  visualBlock: false,
618
618
  lastSelection: null,
619
- lastPastedText: null
619
+ lastPastedText: null,
620
+ // Used by two-character ESC keymap routines. Should not be changed from false here.
621
+ awaitingEscapeSecondCharacter: false
620
622
  };
621
623
  }
622
624
  return cm.state.vim;
@@ -785,17 +787,19 @@
785
787
  * pasted, should it insert itself into a new line, or should the text be
786
788
  * inserted at the cursor position.)
787
789
  */
788
- function Register(text, linewise) {
790
+ function Register(text, linewise, blockwise) {
789
791
  this.clear();
790
792
  this.keyBuffer = [text || ''];
791
793
  this.insertModeChanges = [];
792
794
  this.searchQueries = [];
793
795
  this.linewise = !!linewise;
796
+ this.blockwise = !!blockwise;
794
797
  }
795
798
  Register.prototype = {
796
- setText: function(text, linewise) {
799
+ setText: function(text, linewise, blockwise) {
797
800
  this.keyBuffer = [text || ''];
798
801
  this.linewise = !!linewise;
802
+ this.blockwise = !!blockwise;
799
803
  },
800
804
  pushText: function(text, linewise) {
801
805
  // if this register has ever been set to linewise, use linewise.
@@ -840,7 +844,7 @@
840
844
  registers['/'] = new Register();
841
845
  }
842
846
  RegisterController.prototype = {
843
- pushText: function(registerName, operator, text, linewise) {
847
+ pushText: function(registerName, operator, text, linewise, blockwise) {
844
848
  if (linewise && text.charAt(0) == '\n') {
845
849
  text = text.slice(1) + '\n';
846
850
  }
@@ -857,7 +861,7 @@
857
861
  switch (operator) {
858
862
  case 'yank':
859
863
  // The 0 register contains the text from the most recent yank.
860
- this.registers['0'] = new Register(text, linewise);
864
+ this.registers['0'] = new Register(text, linewise, blockwise);
861
865
  break;
862
866
  case 'delete':
863
867
  case 'change':
@@ -873,7 +877,7 @@
873
877
  break;
874
878
  }
875
879
  // Make sure the unnamed register is set to what just happened
876
- this.unnamedRegister.setText(text, linewise);
880
+ this.unnamedRegister.setText(text, linewise, blockwise);
877
881
  return;
878
882
  }
879
883
 
@@ -882,7 +886,7 @@
882
886
  if (append) {
883
887
  register.pushText(text, linewise);
884
888
  } else {
885
- register.setText(text, linewise);
889
+ register.setText(text, linewise, blockwise);
886
890
  }
887
891
  // The unnamed register always has the same value as the last used
888
892
  // register.
@@ -1860,6 +1864,12 @@
1860
1864
  var curStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
1861
1865
  var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
1862
1866
  var text = cm.getSelection();
1867
+ var visualBlock = vim.visualBlock;
1868
+ if (vim.lastSelection && !vim.visualMode) {
1869
+ visualBlock = vim.lastSelection.visualBlock ? true : visualBlock;
1870
+ }
1871
+ var lastInsertModeChanges = vimGlobalState.macroModeState.lastInsertModeChanges;
1872
+ lastInsertModeChanges.inVisualBlock = visualBlock;
1863
1873
  var replacement = new Array(selections.length).join('1').split('1');
1864
1874
  // save the selectionEnd mark
1865
1875
  var selectionEnd = vim.marks['>'] ? vim.marks['>'].find() : cm.getCursor('head');
@@ -1868,7 +1878,7 @@
1868
1878
  operatorArgs.linewise);
1869
1879
  if (operatorArgs.linewise) {
1870
1880
  // 'C' in visual block extends the block till eol for all lines
1871
- if (vim.visualBlock){
1881
+ if (visualBlock){
1872
1882
  var startLine = curStart.line;
1873
1883
  while (startLine <= curEnd.line) {
1874
1884
  var endCh = lineLength(cm, startLine);
@@ -1898,7 +1908,7 @@
1898
1908
  curEnd = offsetCursor(curEnd, 0, - match[0].length);
1899
1909
  }
1900
1910
  }
1901
- if (vim.visualBlock) {
1911
+ if (visualBlock) {
1902
1912
  cm.replaceSelections(replacement);
1903
1913
  } else {
1904
1914
  cm.setCursor(curStart);
@@ -1916,17 +1926,19 @@
1916
1926
  var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
1917
1927
  // Save the '>' mark before cm.replaceRange clears it.
1918
1928
  var selectionEnd, selectionStart;
1929
+ var blockwise = vim.visualBlock;
1919
1930
  if (vim.visualMode) {
1920
1931
  selectionEnd = vim.marks['>'].find();
1921
1932
  selectionStart = vim.marks['<'].find();
1922
1933
  } else if (vim.lastSelection) {
1923
1934
  selectionEnd = vim.lastSelection.curStartMark.find();
1924
1935
  selectionStart = vim.lastSelection.curEndMark.find();
1936
+ blockwise = vim.lastSelection.visualBlock;
1925
1937
  }
1926
1938
  var text = cm.getSelection();
1927
1939
  vimGlobalState.registerController.pushText(
1928
1940
  operatorArgs.registerName, 'delete', text,
1929
- operatorArgs.linewise);
1941
+ operatorArgs.linewise, blockwise);
1930
1942
  var replacement = new Array(selections.length).join('1').split('1');
1931
1943
  // If the ending line is past the last line, inclusive, instead of
1932
1944
  // including the trailing \n, include the \n before the starting line
@@ -1999,11 +2011,11 @@
1999
2011
  cm.setCursor(cursorIsBefore(curStart, curEnd) ? curStart : curEnd);
2000
2012
  }
2001
2013
  },
2002
- yank: function(cm, operatorArgs, _vim, _curStart, _curEnd, curOriginal) {
2014
+ yank: function(cm, operatorArgs, vim, _curStart, _curEnd, curOriginal) {
2003
2015
  var text = cm.getSelection();
2004
2016
  vimGlobalState.registerController.pushText(
2005
2017
  operatorArgs.registerName, 'yank',
2006
- text, operatorArgs.linewise);
2018
+ text, operatorArgs.linewise, vim.visualBlock);
2007
2019
  cm.setCursor(curOriginal);
2008
2020
  }
2009
2021
  };
@@ -2096,6 +2108,11 @@
2096
2108
  vim.insertMode = true;
2097
2109
  vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
2098
2110
  var insertAt = (actionArgs) ? actionArgs.insertAt : null;
2111
+ if (vim.visualMode) {
2112
+ var selections = getSelectedAreaRange(cm, vim);
2113
+ var selectionStart = selections[0];
2114
+ var selectionEnd = selections[1];
2115
+ }
2099
2116
  if (insertAt == 'eol') {
2100
2117
  var cursor = cm.getCursor();
2101
2118
  cursor = Pos(cursor.line, lineLength(cm, cursor.line));
@@ -2103,15 +2120,34 @@
2103
2120
  } else if (insertAt == 'charAfter') {
2104
2121
  cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
2105
2122
  } else if (insertAt == 'firstNonBlank') {
2106
- cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
2123
+ if (vim.visualMode && !vim.visualBlock) {
2124
+ if (selectionEnd.line < selectionStart.line) {
2125
+ cm.setCursor(selectionEnd);
2126
+ } else {
2127
+ selectionStart = Pos(selectionStart.line, 0);
2128
+ cm.setCursor(selectionStart);
2129
+ }
2130
+ cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
2131
+ } else if (vim.visualBlock) {
2132
+ selectionEnd = Pos(selectionEnd.line, selectionStart.ch);
2133
+ cm.setCursor(selectionStart);
2134
+ selectBlock(cm, selectionEnd);
2135
+ } else {
2136
+ cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
2137
+ }
2107
2138
  } else if (insertAt == 'endOfSelectedArea') {
2108
- var selectionEnd = cm.getCursor('head');
2109
- var selectionStart = cm.getCursor('anchor');
2110
- if (selectionEnd.line < selectionStart.line) {
2139
+ if (vim.visualBlock) {
2140
+ selectionStart = Pos(selectionStart.line, selectionEnd.ch);
2141
+ cm.setCursor(selectionStart);
2142
+ selectBlock(cm, selectionEnd);
2143
+ } else if (selectionEnd.line < selectionStart.line) {
2111
2144
  selectionEnd = Pos(selectionStart.line, 0);
2145
+ cm.setCursor(selectionEnd);
2146
+ }
2147
+ } else if (insertAt == 'inplace') {
2148
+ if (vim.visualMode){
2149
+ return;
2112
2150
  }
2113
- cm.setCursor(selectionEnd);
2114
- exitVisualMode(cm);
2115
2151
  }
2116
2152
  cm.setOption('keyMap', 'vim-insert');
2117
2153
  cm.setOption('disableInput', false);
@@ -2129,6 +2165,9 @@
2129
2165
  cm.on('change', onChange);
2130
2166
  CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
2131
2167
  }
2168
+ if (vim.visualMode) {
2169
+ exitVisualMode(cm);
2170
+ }
2132
2171
  },
2133
2172
  toggleVisualMode: function(cm, actionArgs, vim) {
2134
2173
  var repeat = actionArgs.repeat;
@@ -2339,6 +2378,7 @@
2339
2378
  var text = Array(actionArgs.repeat + 1).join(text);
2340
2379
  }
2341
2380
  var linewise = register.linewise;
2381
+ var blockwise = register.blockwise;
2342
2382
  if (linewise) {
2343
2383
  if(vim.visualMode) {
2344
2384
  text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n';
@@ -2351,6 +2391,12 @@
2351
2391
  cur.ch = 0;
2352
2392
  }
2353
2393
  } else {
2394
+ if (blockwise) {
2395
+ text = text.split('\n');
2396
+ for (var i = 0; i < text.length; i++) {
2397
+ text[i] = (text[i] == '') ? ' ' : text[i];
2398
+ }
2399
+ }
2354
2400
  cur.ch += actionArgs.after ? 1 : 0;
2355
2401
  }
2356
2402
  var curPosFinal;
@@ -2362,32 +2408,75 @@
2362
2408
  var selectedArea = getSelectedAreaRange(cm, vim);
2363
2409
  var selectionStart = selectedArea[0];
2364
2410
  var selectionEnd = selectedArea[1];
2411
+ var selectedText = cm.getSelection();
2412
+ var selections = cm.listSelections();
2413
+ var emptyStrings = new Array(selections.length).join('1').split('1');
2365
2414
  // save the curEnd marker before it get cleared due to cm.replaceRange.
2366
- if (vim.lastSelection) lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
2415
+ if (vim.lastSelection) {
2416
+ lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
2417
+ }
2367
2418
  // push the previously selected text to unnamed register
2368
- vimGlobalState.registerController.unnamedRegister.setText(cm.getRange(selectionStart, selectionEnd));
2369
- cm.replaceRange(text, selectionStart, selectionEnd);
2419
+ vimGlobalState.registerController.unnamedRegister.setText(selectedText);
2420
+ if (blockwise) {
2421
+ // first delete the selected text
2422
+ cm.replaceSelections(emptyStrings);
2423
+ // Set new selections as per the block length of the yanked text
2424
+ selectionEnd = Pos(selectionStart.line + text.length-1, selectionStart.ch);
2425
+ cm.setCursor(selectionStart);
2426
+ selectBlock(cm, selectionEnd);
2427
+ cm.replaceSelections(text);
2428
+ curPosFinal = selectionStart;
2429
+ } else if (vim.visualBlock) {
2430
+ cm.replaceSelections(emptyStrings);
2431
+ cm.setCursor(selectionStart);
2432
+ cm.replaceRange(text, selectionStart, selectionStart);
2433
+ curPosFinal = selectionStart;
2434
+ } else {
2435
+ cm.replaceRange(text, selectionStart, selectionEnd);
2436
+ curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1);
2437
+ }
2370
2438
  // restore the the curEnd marker
2371
- if(lastSelectionCurEnd) vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd);
2372
- curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1);
2373
- if(linewise)curPosFinal.ch=0;
2439
+ if(lastSelectionCurEnd) {
2440
+ vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd);
2441
+ }
2442
+ if (linewise) {
2443
+ curPosFinal.ch=0;
2444
+ }
2374
2445
  } else {
2375
- cm.replaceRange(text, cur);
2376
- // Now fine tune the cursor to where we want it.
2377
- if (linewise && actionArgs.after) {
2378
- curPosFinal = Pos(
2446
+ if (blockwise) {
2447
+ cm.setCursor(cur);
2448
+ for (var i = 0; i < text.length; i++) {
2449
+ var line = cur.line+i;
2450
+ if (line > cm.lastLine()) {
2451
+ cm.replaceRange('\n', Pos(line, 0));
2452
+ }
2453
+ var lastCh = lineLength(cm, line);
2454
+ if (lastCh < cur.ch) {
2455
+ extendLineToColumn(cm, line, cur.ch);
2456
+ }
2457
+ }
2458
+ cm.setCursor(cur);
2459
+ selectBlock(cm, Pos(cur.line + text.length-1, cur.ch));
2460
+ cm.replaceSelections(text);
2461
+ curPosFinal = cur;
2462
+ } else {
2463
+ cm.replaceRange(text, cur);
2464
+ // Now fine tune the cursor to where we want it.
2465
+ if (linewise && actionArgs.after) {
2466
+ curPosFinal = Pos(
2379
2467
  cur.line + 1,
2380
2468
  findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)));
2381
- } else if (linewise && !actionArgs.after) {
2382
- curPosFinal = Pos(
2383
- cur.line,
2384
- findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
2385
- } else if (!linewise && actionArgs.after) {
2386
- idx = cm.indexFromPos(cur);
2387
- curPosFinal = cm.posFromIndex(idx + text.length - 1);
2388
- } else {
2389
- idx = cm.indexFromPos(cur);
2390
- curPosFinal = cm.posFromIndex(idx + text.length);
2469
+ } else if (linewise && !actionArgs.after) {
2470
+ curPosFinal = Pos(
2471
+ cur.line,
2472
+ findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
2473
+ } else if (!linewise && actionArgs.after) {
2474
+ idx = cm.indexFromPos(cur);
2475
+ curPosFinal = cm.posFromIndex(idx + text.length - 1);
2476
+ } else {
2477
+ idx = cm.indexFromPos(cur);
2478
+ curPosFinal = cm.posFromIndex(idx + text.length);
2479
+ }
2391
2480
  }
2392
2481
  }
2393
2482
  cm.setCursor(curPosFinal);
@@ -2597,6 +2686,12 @@
2597
2686
  function escapeRegex(s) {
2598
2687
  return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
2599
2688
  }
2689
+ function extendLineToColumn(cm, lineNum, column) {
2690
+ var endCh = lineLength(cm, lineNum);
2691
+ var spaces = new Array(column-endCh+1).join(' ');
2692
+ cm.setCursor(Pos(lineNum, endCh));
2693
+ cm.replaceRange(spaces, cm.getCursor());
2694
+ }
2600
2695
  // This functions selects a rectangular block
2601
2696
  // of text with selectionEnd as any of its corner
2602
2697
  // Height of block:
@@ -2606,55 +2701,86 @@
2606
2701
  function selectBlock(cm, selectionEnd) {
2607
2702
  var selections = [], ranges = cm.listSelections();
2608
2703
  var firstRange = ranges[0].anchor, lastRange = ranges[ranges.length-1].anchor;
2609
- var start, end, selectionStart;
2704
+ var start, end, direction, selectionStart;
2610
2705
  var curEnd = cm.getCursor('head');
2706
+ var originalSelectionEnd = copyCursor(selectionEnd);
2707
+ start = firstRange.line;
2708
+ end = lastRange.line;
2709
+ if (selectionEnd.line < curEnd.line) {
2710
+ direction = 'up';
2711
+ } else if (selectionEnd.line > curEnd.line) {
2712
+ direction = 'down';
2713
+ } else {
2714
+ if (selectionEnd.ch != curEnd.ch) {
2715
+ direction = selectionEnd.ch > curEnd.ch ? 'right' : 'left';
2716
+ }
2717
+ selectionStart = cm.getCursor('anchor');
2718
+ }
2611
2719
  var primIndex = getIndex(ranges, curEnd);
2612
2720
  // sets to true when selectionEnd already lies inside the existing selections
2613
- var contains = getIndex(ranges, selectionEnd) < 0 ? false : true;
2614
2721
  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;
2722
+ var contains = getIndex(ranges, selectionEnd) < 0 ? false : true;
2723
+ var isClipped = !cursorEqual(originalSelectionEnd, selectionEnd);
2724
+ // This function helps to check selection crossing
2725
+ // in case of short lines.
2726
+ var processSelectionCrossing = function() {
2727
+ if (isClipped) {
2728
+ if (curEnd.ch >= selectionStart.ch) {
2729
+ selectionStart.ch++;
2730
+ }
2731
+ } else if (curEnd.ch == lineLength(cm, curEnd.line)) {
2732
+ if (cursorEqual(ranges[primIndex].anchor, ranges[primIndex].head) && ranges.length>1) {
2733
+ if (direction == 'up') {
2734
+ if (contains || primIndex>0) {
2735
+ start = firstRange.line;
2736
+ end = selectionEnd.line;
2737
+ selectionStart = ranges[primIndex-1].anchor;
2738
+ }
2739
+ } else {
2740
+ if (contains || primIndex == 0) {
2741
+ end = lastRange.line;
2742
+ start = selectionEnd.line;
2743
+ selectionStart = ranges[primIndex+1].anchor;
2744
+ }
2745
+ }
2746
+ if (selectionEnd.ch >= selectionStart.ch) {
2747
+ selectionStart.ch--;
2748
+ }
2749
+ }
2628
2750
  }
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;
2751
+ };
2752
+ switch(direction) {
2753
+ case 'up':
2754
+ start = contains ? firstRange.line : selectionEnd.line;
2755
+ end = contains ? selectionEnd.line : lastRange.line;
2756
+ selectionStart = lastRange;
2757
+ processSelectionCrossing();
2758
+ break;
2759
+ case 'down':
2760
+ start = contains ? selectionEnd.line : firstRange.line;
2761
+ end = contains ? lastRange.line : selectionEnd.line;
2762
+ selectionStart = firstRange;
2763
+ processSelectionCrossing();
2764
+ break;
2765
+ case 'left':
2766
+ if ((selectionEnd.ch <= selectionStart.ch) && (curEnd.ch > selectionStart.ch)) {
2767
+ selectionStart.ch++;
2768
+ selectionEnd.ch--;
2769
+ }
2770
+ break;
2771
+ case 'right':
2772
+ if ((selectionStart.ch <= selectionEnd.ch) && (curEnd.ch < selectionStart.ch)) {
2773
+ selectionStart.ch--;
2774
+ selectionEnd.ch++;
2775
+ }
2776
+ break;
2777
+ default:
2778
+ start = selectionStart.line;
2637
2779
  end = selectionEnd.line;
2638
- }
2639
- }
2640
- if (start > end) {
2641
- var tmp = start;
2642
- start = end;
2643
- end = tmp;
2644
2780
  }
2645
- selectionStart = (near > 0) ? firstRange : lastRange;
2646
2781
  while (start <= end) {
2647
2782
  var anchor = {line: start, ch: selectionStart.ch};
2648
2783
  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
2784
  var range = {anchor: anchor, head: head};
2659
2785
  selections.push(range);
2660
2786
  if (cursorEqual(head, selectionEnd)) {
@@ -2666,16 +2792,29 @@
2666
2792
  // after selection crossing
2667
2793
  selectionEnd.ch = selections[0].head.ch;
2668
2794
  selectionStart.ch = selections[0].anchor.ch;
2795
+ if (cursorEqual(selectionEnd, selections[0].head)) {
2796
+ selectionStart.line = selections[selections.length-1].anchor.line;
2797
+ } else {
2798
+ selectionStart.line = selections[0].anchor.line;
2799
+ }
2669
2800
  cm.setSelections(selections, primIndex);
2670
2801
  return selectionStart;
2671
2802
  }
2672
- function getIndex(ranges, head) {
2803
+ // getIndex returns the index of the cursor in the selections.
2804
+ function getIndex(ranges, cursor, end) {
2805
+ var pos = -1;
2673
2806
  for (var i = 0; i < ranges.length; i++) {
2674
- if (cursorEqual(ranges[i].head, head)) {
2675
- return i;
2807
+ var atAnchor = cursorEqual(ranges[i].anchor, cursor);
2808
+ var atHead = cursorEqual(ranges[i].head, cursor);
2809
+ if (end == 'head') {
2810
+ pos = atHead ? i : pos;
2811
+ } else if (end == 'anchor') {
2812
+ pos = atAnchor ? i : pos;
2813
+ } else {
2814
+ pos = (atAnchor || atHead) ? i : pos;
2676
2815
  }
2677
2816
  }
2678
- return -1;
2817
+ return pos;
2679
2818
  }
2680
2819
  function getSelectedAreaRange(cm, vim) {
2681
2820
  var lastSelection = vim.lastSelection;
@@ -2739,7 +2878,7 @@
2739
2878
  var ranges = cm.listSelections();
2740
2879
  // This check ensures to set the cursor
2741
2880
  // position where we left off in previous selection
2742
- var swap = getIndex(ranges, selectionStart) > -1;
2881
+ var swap = getIndex(ranges, selectionStart, 'head') > -1;
2743
2882
  if (vim.visualBlock) {
2744
2883
  var height = Math.abs(selectionStart.line - selectionEnd.line)+1;
2745
2884
  var width = Math.abs(selectionStart.ch - selectionEnd.ch);
@@ -2759,14 +2898,16 @@
2759
2898
  var vim = cm.state.vim;
2760
2899
  var selectionStart = cm.getCursor('anchor');
2761
2900
  var selectionEnd = cm.getCursor('head');
2901
+ // hack to place the cursor at the right place
2902
+ // in case of visual block
2903
+ if (vim.visualBlock && (cursorIsBefore(selectionStart, selectionEnd))) {
2904
+ selectionEnd.ch--;
2905
+ }
2762
2906
  updateLastSelection(cm, vim);
2763
2907
  vim.visualMode = false;
2764
2908
  vim.visualLine = false;
2765
2909
  vim.visualBlock = false;
2766
2910
  if (!cursorEqual(selectionStart, selectionEnd)) {
2767
- // Clear the selection and set the cursor only if the selection has not
2768
- // already been cleared. Otherwise we risk moving the cursor somewhere
2769
- // it's not supposed to be.
2770
2911
  cm.setCursor(clipCursorToContent(cm, selectionEnd));
2771
2912
  }
2772
2913
  CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
@@ -4498,7 +4639,32 @@
4498
4639
  var macroModeState = vimGlobalState.macroModeState;
4499
4640
  var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');
4500
4641
  var isPlaying = macroModeState.isPlaying;
4642
+ var lastChange = macroModeState.lastInsertModeChanges;
4643
+ // In case of visual block, the insertModeChanges are not saved as a
4644
+ // single word, so we convert them to a single word
4645
+ // so as to update the ". register as expected in real vim.
4646
+ var text = [];
4501
4647
  if (!isPlaying) {
4648
+ var selLength = lastChange.inVisualBlock ? vim.lastSelection.visualBlock.height : 1;
4649
+ var changes = lastChange.changes;
4650
+ var text = [];
4651
+ var i = 0;
4652
+ // In case of multiple selections in blockwise visual,
4653
+ // the inserted text, for example: 'f<Backspace>oo', is stored as
4654
+ // 'f', 'f', InsertModeKey 'o', 'o', 'o', 'o'. (if you have a block with 2 lines).
4655
+ // We push the contents of the changes array as per the following:
4656
+ // 1. In case of InsertModeKey, just increment by 1.
4657
+ // 2. In case of a character, jump by selLength (2 in the example).
4658
+ while (i < changes.length) {
4659
+ // This loop will convert 'ff<bs>oooo' to 'f<bs>oo'.
4660
+ text.push(changes[i]);
4661
+ if (changes[i] instanceof InsertModeKey) {
4662
+ i++;
4663
+ } else {
4664
+ i+= selLength;
4665
+ }
4666
+ }
4667
+ lastChange.changes = text;
4502
4668
  cm.off('change', onChange);
4503
4669
  CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
4504
4670
  }
@@ -4515,13 +4681,64 @@
4515
4681
  cm.setOption('disableInput', true);
4516
4682
  cm.toggleOverwrite(false); // exit replace mode if we were in it.
4517
4683
  // update the ". register before exiting insert mode
4518
- insertModeChangeRegister.setText(macroModeState.lastInsertModeChanges.changes.join(''));
4684
+ insertModeChangeRegister.setText(lastChange.changes.join(''));
4519
4685
  CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
4520
4686
  if (macroModeState.isRecording) {
4521
4687
  logInsertModeChange(macroModeState);
4522
4688
  }
4523
4689
  }
4524
4690
 
4691
+ defineOption('enableInsertModeEscKeys', false, 'boolean');
4692
+ // Use this option to customize the two-character ESC keymap.
4693
+ // If you want to use characters other than i j or k you'll have to add
4694
+ // lines to the vim-insert and await-second keymaps later in this file.
4695
+ defineOption('insertModeEscKeys', 'kj', 'string');
4696
+ // The timeout in milliseconds for the two-character ESC keymap should be
4697
+ // adjusted according to your typing speed to prevent false positives.
4698
+ defineOption('insertModeEscKeysTimeout', 200, 'number');
4699
+ function firstEscCharacterHandler(ch) {
4700
+ return function(cm){
4701
+ var keys = getOption('insertModeEscKeys');
4702
+ var firstEscCharacter = keys && keys.length > 1 && keys.charAt(0);
4703
+ if (!getOption('enableInsertModeEscKeys')|| firstEscCharacter !== ch) {
4704
+ return CodeMirror.Pass;
4705
+ } else {
4706
+ cm.replaceRange(ch, cm.getCursor(), cm.getCursor(), "+input");
4707
+ cm.setOption('keyMap', 'await-second');
4708
+ cm.state.vim.awaitingEscapeSecondCharacter = true;
4709
+ setTimeout(
4710
+ function(){
4711
+ if(cm.state.vim.awaitingEscapeSecondCharacter) {
4712
+ cm.state.vim.awaitingEscapeSecondCharacter = false;
4713
+ cm.setOption('keyMap', 'vim-insert');
4714
+ }
4715
+ },
4716
+ getOption('insertModeEscKeysTimeout'));
4717
+ }
4718
+ };
4719
+ }
4720
+ function secondEscCharacterHandler(ch){
4721
+ return function(cm) {
4722
+ var keys = getOption('insertModeEscKeys');
4723
+ var secondEscCharacter = keys && keys.length > 1 && keys.charAt(1);
4724
+ if (!getOption('enableInsertModeEscKeys')|| secondEscCharacter !== ch) {
4725
+ return CodeMirror.Pass;
4726
+ // This is not the handler you're looking for. Just insert as usual.
4727
+ } else {
4728
+ if (cm.state.vim.insertMode) {
4729
+ var lastChange = vimGlobalState.macroModeState.lastInsertModeChanges;
4730
+ if (lastChange && lastChange.changes.length) {
4731
+ lastChange.changes.pop();
4732
+ }
4733
+ }
4734
+ cm.state.vim.awaitingEscapeSecondCharacter = false;
4735
+ cm.replaceRange('', {ch: cm.getCursor().ch - 1, line: cm.getCursor().line},
4736
+ cm.getCursor(), "+input");
4737
+ exitInsertMode(cm);
4738
+ }
4739
+ };
4740
+ }
4741
+
4525
4742
  CodeMirror.keyMap['vim-insert'] = {
4526
4743
  // TODO: override navigation keys so that Esc will cancel automatic
4527
4744
  // indentation from o, O, i_<CR>
@@ -4535,9 +4752,23 @@
4535
4752
  CodeMirror.commands.newlineAndIndent;
4536
4753
  fn(cm);
4537
4754
  },
4755
+ // The next few lines are where you'd add additional handlers if
4756
+ // you wanted to use keys other than i j and k for two-character
4757
+ // escape sequences. Don't forget to add them in the await-second
4758
+ // section as well.
4759
+ "'i'": firstEscCharacterHandler('i'),
4760
+ "'j'": firstEscCharacterHandler('j'),
4761
+ "'k'": firstEscCharacterHandler('k'),
4538
4762
  fallthrough: ['default']
4539
4763
  };
4540
4764
 
4765
+ CodeMirror.keyMap['await-second'] = {
4766
+ "'i'": secondEscCharacterHandler('i'),
4767
+ "'j'": secondEscCharacterHandler('j'),
4768
+ "'k'": secondEscCharacterHandler('k'),
4769
+ fallthrough: ['vim-insert']
4770
+ };
4771
+
4541
4772
  CodeMirror.keyMap['vim-replace'] = {
4542
4773
  'Backspace': 'goCharLeft',
4543
4774
  fallthrough: ['vim-insert']
@@ -4710,11 +4941,7 @@
4710
4941
  // insert mode changes. Will conform to that behavior.
4711
4942
  repeat = !vim.lastEditActionCommand ? 1 : repeat;
4712
4943
  var changeObject = macroModeState.lastInsertModeChanges;
4713
- // This isn't strictly necessary, but since lastInsertModeChanges is
4714
- // supposed to be immutable during replay, this helps catch bugs.
4715
- macroModeState.lastInsertModeChanges = {};
4716
4944
  repeatInsertModeChanges(cm, changeObject.changes, repeat);
4717
- macroModeState.lastInsertModeChanges = changeObject;
4718
4945
  }
4719
4946
  }
4720
4947
  vim.inputState = vim.lastEditInputState;
@@ -4752,6 +4979,18 @@
4752
4979
  }
4753
4980
  return true;
4754
4981
  }
4982
+ var curStart = cm.getCursor();
4983
+ var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock;
4984
+ if (inVisualBlock) {
4985
+ // Set up block selection again for repeating the changes.
4986
+ var vim = cm.state.vim;
4987
+ var block = vim.lastSelection.visualBlock;
4988
+ var curEnd = Pos(curStart.line + block.height-1, curStart.ch);
4989
+ cm.setCursor(curStart);
4990
+ selectBlock(cm, curEnd);
4991
+ repeat = cm.listSelections().length;
4992
+ cm.setCursor(curStart);
4993
+ }
4755
4994
  for (var i = 0; i < repeat; i++) {
4756
4995
  for (var j = 0; j < changes.length; j++) {
4757
4996
  var change = changes[j];
@@ -4762,6 +5001,10 @@
4762
5001
  cm.replaceRange(change, cur, cur);
4763
5002
  }
4764
5003
  }
5004
+ if (inVisualBlock) {
5005
+ curStart.line++;
5006
+ cm.setCursor(curStart);
5007
+ }
4765
5008
  }
4766
5009
  }
4767
5010