codemirror-rails 4.4 → 4.5

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