codemirror-rails 3.13 → 3.14

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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/codemirror/rails/version.rb +2 -2
  3. data/vendor/assets/javascripts/codemirror.js +328 -250
  4. data/vendor/assets/javascripts/codemirror/addons/comment/comment.js +7 -6
  5. data/vendor/assets/javascripts/codemirror/addons/edit/closebrackets.js +33 -7
  6. data/vendor/assets/javascripts/codemirror/addons/edit/matchbrackets.js +14 -10
  7. data/vendor/assets/javascripts/codemirror/addons/edit/trailingspace.js +15 -0
  8. data/vendor/assets/javascripts/codemirror/addons/fold/brace-fold.js +70 -17
  9. data/vendor/assets/javascripts/codemirror/addons/fold/foldcode.js +56 -20
  10. data/vendor/assets/javascripts/codemirror/addons/fold/xml-fold.js +135 -39
  11. data/vendor/assets/javascripts/codemirror/addons/hint/html-hint.js +324 -571
  12. data/vendor/assets/javascripts/codemirror/addons/hint/show-hint.js +199 -109
  13. data/vendor/assets/javascripts/codemirror/addons/hint/xml-hint.js +60 -113
  14. data/vendor/assets/javascripts/codemirror/addons/lint/coffeescript-lint.js +24 -0
  15. data/vendor/assets/javascripts/codemirror/addons/merge/merge.js +431 -0
  16. data/vendor/assets/javascripts/codemirror/addons/mode/multiplex.js +7 -1
  17. data/vendor/assets/javascripts/codemirror/addons/search/match-highlighter.js +46 -20
  18. data/vendor/assets/javascripts/codemirror/addons/search/search.js +1 -1
  19. data/vendor/assets/javascripts/codemirror/keymaps/emacs.js +370 -13
  20. data/vendor/assets/javascripts/codemirror/keymaps/extra.js +43 -0
  21. data/vendor/assets/javascripts/codemirror/keymaps/vim.js +535 -214
  22. data/vendor/assets/javascripts/codemirror/modes/clike.js +56 -0
  23. data/vendor/assets/javascripts/codemirror/modes/javascript.js +19 -14
  24. data/vendor/assets/javascripts/codemirror/modes/markdown.js +2 -2
  25. data/vendor/assets/javascripts/codemirror/modes/ruby.js +67 -16
  26. data/vendor/assets/javascripts/codemirror/modes/smarty.js +167 -110
  27. data/vendor/assets/javascripts/codemirror/modes/sql.js +97 -15
  28. data/vendor/assets/javascripts/codemirror/modes/xml.js +14 -18
  29. data/vendor/assets/stylesheets/codemirror.css +0 -1
  30. data/vendor/assets/stylesheets/codemirror/addons/merge/merge.css +82 -0
  31. metadata +7 -2
@@ -0,0 +1,43 @@
1
+ // A number of additional default bindings that are too obscure to
2
+ // include in the core codemirror.js file.
3
+
4
+ (function() {
5
+ "use strict";
6
+
7
+ var Pos = CodeMirror.Pos;
8
+
9
+ function moveLines(cm, start, end, dist) {
10
+ if (!dist || start > end) return 0;
11
+
12
+ var from = cm.clipPos(Pos(start, 0)), to = cm.clipPos(Pos(end));
13
+ var text = cm.getRange(from, to);
14
+
15
+ if (start <= cm.firstLine())
16
+ cm.replaceRange("", from, Pos(to.line + 1, 0));
17
+ else
18
+ cm.replaceRange("", Pos(from.line - 1), to);
19
+ var target = from.line + dist;
20
+ if (target <= cm.firstLine()) {
21
+ cm.replaceRange(text + "\n", Pos(target, 0));
22
+ return cm.firstLine() - from.line;
23
+ } else {
24
+ var targetPos = cm.clipPos(Pos(target - 1));
25
+ cm.replaceRange("\n" + text, targetPos);
26
+ return targetPos.line + 1 - from.line;
27
+ }
28
+ }
29
+
30
+ function moveSelectedLines(cm, dist) {
31
+ var head = cm.getCursor("head"), anchor = cm.getCursor("anchor");
32
+ cm.operation(function() {
33
+ var moved = moveLines(cm, Math.min(head.line, anchor.line), Math.max(head.line, anchor.line), dist);
34
+ cm.setSelection(Pos(anchor.line + moved, anchor.ch), Pos(head.line + moved, head.ch));
35
+ });
36
+ }
37
+
38
+ CodeMirror.commands.moveLinesUp = function(cm) { moveSelectedLines(cm, -1); };
39
+ CodeMirror.commands.moveLinesDown = function(cm) { moveSelectedLines(cm, 1); };
40
+
41
+ CodeMirror.keyMap["default"]["Alt-Up"] = "moveLinesUp";
42
+ CodeMirror.keyMap["default"]["Alt-Down"] = "moveLinesDown";
43
+ })();
@@ -190,8 +190,8 @@
190
190
  motionArgs: {toJumplist: true}},
191
191
  { keys: ['`', 'character'], type: 'motion', motion: 'goToMark',
192
192
  motionArgs: {toJumplist: true}},
193
- { keys: [']', '`',], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
194
- { keys: ['[', '`',], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
193
+ { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
194
+ { keys: ['[', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
195
195
  { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
196
196
  { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
197
197
  { keys: [']', 'character'], type: 'motion',
@@ -241,34 +241,38 @@
241
241
  actionArgs: { forward: true }},
242
242
  { keys: ['<C-o>'], type: 'action', action: 'jumpListWalk',
243
243
  actionArgs: { forward: false }},
244
- { keys: ['a'], type: 'action', action: 'enterInsertMode',
244
+ { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true,
245
245
  actionArgs: { insertAt: 'charAfter' }},
246
- { keys: ['A'], type: 'action', action: 'enterInsertMode',
246
+ { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true,
247
247
  actionArgs: { insertAt: 'eol' }},
248
- { keys: ['i'], type: 'action', action: 'enterInsertMode',
248
+ { keys: ['i'], type: 'action', action: 'enterInsertMode', isEdit: true,
249
249
  actionArgs: { insertAt: 'inplace' }},
250
- { keys: ['I'], type: 'action', action: 'enterInsertMode',
250
+ { keys: ['I'], type: 'action', action: 'enterInsertMode', isEdit: true,
251
251
  actionArgs: { insertAt: 'firstNonBlank' }},
252
252
  { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode',
253
+ isEdit: true, interlaceInsertRepeat: true,
253
254
  actionArgs: { after: true }},
254
255
  { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode',
256
+ isEdit: true, interlaceInsertRepeat: true,
255
257
  actionArgs: { after: false }},
256
258
  { keys: ['v'], type: 'action', action: 'toggleVisualMode' },
257
259
  { keys: ['V'], type: 'action', action: 'toggleVisualMode',
258
260
  actionArgs: { linewise: true }},
259
- { keys: ['J'], type: 'action', action: 'joinLines' },
260
- { keys: ['p'], type: 'action', action: 'paste',
261
- actionArgs: { after: true }},
262
- { keys: ['P'], type: 'action', action: 'paste',
263
- actionArgs: { after: false }},
264
- { keys: ['r', 'character'], type: 'action', action: 'replace' },
261
+ { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true },
262
+ { keys: ['p'], type: 'action', action: 'paste', isEdit: true,
263
+ actionArgs: { after: true, isEdit: true }},
264
+ { keys: ['P'], type: 'action', action: 'paste', isEdit: true,
265
+ actionArgs: { after: false, isEdit: true }},
266
+ { keys: ['r', 'character'], type: 'action', action: 'replace', isEdit: true },
265
267
  { keys: ['@', 'character'], type: 'action', action: 'replayMacro' },
266
268
  { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' },
267
- { keys: ['R'], type: 'action', action: 'enterReplaceMode' },
269
+ // Handle Replace-mode as a special case of insert mode.
270
+ { keys: ['R'], type: 'action', action: 'enterInsertMode', isEdit: true,
271
+ actionArgs: { replace: true }},
268
272
  { keys: ['u'], type: 'action', action: 'undo' },
269
273
  { keys: ['<C-r>'], type: 'action', action: 'redo' },
270
274
  { keys: ['m', 'character'], type: 'action', action: 'setMark' },
271
- { keys: ['\"', 'character'], type: 'action', action: 'setRegister' },
275
+ { keys: ['"', 'character'], type: 'action', action: 'setRegister' },
272
276
  { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor',
273
277
  actionArgs: { position: 'center' }},
274
278
  { keys: ['z', '.'], type: 'action', action: 'scrollToCursor',
@@ -286,8 +290,10 @@
286
290
  motion: 'moveToFirstNonWhiteSpaceCharacter' },
287
291
  { keys: ['.'], type: 'action', action: 'repeatLastEdit' },
288
292
  { keys: ['<C-a>'], type: 'action', action: 'incrementNumberToken',
293
+ isEdit: true,
289
294
  actionArgs: {increase: true, backtrack: false}},
290
295
  { keys: ['<C-x>'], type: 'action', action: 'incrementNumberToken',
296
+ isEdit: true,
291
297
  actionArgs: {increase: false, backtrack: false}},
292
298
  // Text object motions
293
299
  { keys: ['a', 'character'], type: 'motion',
@@ -309,9 +315,7 @@
309
315
  ];
310
316
 
311
317
  var Vim = function() {
312
- var alphabetRegex = /[A-Za-z]/;
313
318
  var numberRegex = /[\d]/;
314
- var whiteSpaceRegex = /\s/;
315
319
  var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
316
320
  function makeKeyRange(start, size) {
317
321
  var keys = [];
@@ -323,18 +327,12 @@
323
327
  var upperCaseAlphabet = makeKeyRange(65, 26);
324
328
  var lowerCaseAlphabet = makeKeyRange(97, 26);
325
329
  var numbers = makeKeyRange(48, 10);
326
- var SPECIAL_SYMBOLS = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\'';
327
- var specialSymbols = SPECIAL_SYMBOLS.split('');
330
+ var specialSymbols = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;"\''.split('');
328
331
  var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace',
329
332
  'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter'];
330
- var validMarks = upperCaseAlphabet.concat(lowerCaseAlphabet).concat(
331
- numbers).concat(['<', '>']);
332
- var validRegisters = upperCaseAlphabet.concat(lowerCaseAlphabet).concat(
333
- numbers).concat('-\"'.split(''));
333
+ var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
334
+ var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"']);
334
335
 
335
- function isAlphabet(k) {
336
- return alphabetRegex.test(k);
337
- }
338
336
  function isLine(cm, line) {
339
337
  return line >= cm.firstLine() && line <= cm.lastLine();
340
338
  }
@@ -350,18 +348,9 @@
350
348
  function isUpperCase(k) {
351
349
  return (/^[A-Z]$/).test(k);
352
350
  }
353
- function isAlphanumeric(k) {
354
- return (/^[\w]$/).test(k);
355
- }
356
- function isWhiteSpace(k) {
357
- return whiteSpaceRegex.test(k);
358
- }
359
351
  function isWhiteSpaceString(k) {
360
352
  return (/^\s*$/).test(k);
361
353
  }
362
- function inRangeInclusive(x, start, end) {
363
- return x >= start && x <= end;
364
- }
365
354
  function inArray(val, arr) {
366
355
  for (var i = 0; i < arr.length; i++) {
367
356
  if (arr[i] == val) {
@@ -441,6 +430,11 @@
441
430
  return {
442
431
  macroKeyBuffer: [],
443
432
  latestRegister: undefined,
433
+ inReplay: false,
434
+ lastInsertModeChanges: {
435
+ changes: [], // Change list
436
+ expectCursorActivityForChange: false // Set to true on change, false on cursorActivity.
437
+ },
444
438
  enteredMacroMode: undefined,
445
439
  isMacroPlaying: false,
446
440
  toggle: function(cm, registerName) {
@@ -453,8 +447,8 @@
453
447
  '(recording)['+registerName+']', null, {bottom:true});
454
448
  }
455
449
  }
456
- }
457
- }
450
+ };
451
+ };
458
452
 
459
453
  // Global Vim state. Call getVimGlobalState to get and initialize.
460
454
  var vimGlobalState;
@@ -479,6 +473,12 @@
479
473
  // Store instance state in the CodeMirror object.
480
474
  cm.vimState = {
481
475
  inputState: new InputState(),
476
+ // Vim's input state that triggered the last edit, used to repeat
477
+ // motions and operators with '.'.
478
+ lastEditInputState: undefined,
479
+ // Vim's action command before the last edit, used to repeat actions
480
+ // with '.' and insert mode repeat.
481
+ lastEditActionCommand: undefined,
482
482
  // When using jk for navigation, if you move from a longer line to a
483
483
  // shorter line, the cursor may clip to the end of the shorter line.
484
484
  // If j is pressed again and cursor goes to the next line, the
@@ -491,6 +491,10 @@
491
491
  // executed in between.
492
492
  lastMotion: null,
493
493
  marks: {},
494
+ insertMode: false,
495
+ // Repeat count for changes made in insert mode, triggered by key
496
+ // sequences like 3,i. Only exists when insertMode is true.
497
+ insertModeRepeat: undefined,
494
498
  visualMode: false,
495
499
  // If we are in visual line mode. No effect if visualMode is false.
496
500
  visualLine: false
@@ -512,15 +516,21 @@
512
516
  clearVimGlobalState_: function() {
513
517
  vimGlobalState = null;
514
518
  },
519
+ // Testing hook.
520
+ getVimGlobalState_: function() {
521
+ return vimGlobalState;
522
+ },
523
+ InsertModeKey: InsertModeKey,
515
524
  map: function(lhs, rhs) {
516
525
  // Add user defined key bindings.
517
526
  exCommandDispatcher.map(lhs, rhs);
518
527
  },
519
528
  defineEx: function(name, prefix, func){
520
- if (name.indexOf(prefix) === 0) {
521
- exCommands[name]=func;
522
- exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
523
- }else throw new Error("(Vim.defineEx) \""+prefix+"\" is not a prefix of \""+name+"\", command not registered");
529
+ if (name.indexOf(prefix) !== 0) {
530
+ throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
531
+ }
532
+ exCommands[name]=func;
533
+ exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
524
534
  },
525
535
  // Initializes vim state variable on the CodeMirror object. Should only be
526
536
  // called lazily by handleKey or for testing.
@@ -536,9 +546,9 @@
536
546
  if (macroModeState.enteredMacroMode) {
537
547
  if (key == 'q') {
538
548
  actions.exitMacroRecordMode();
549
+ vim.inputState = new InputState();
539
550
  return;
540
551
  }
541
- logKey(macroModeState, key);
542
552
  }
543
553
  if (key == '<Esc>') {
544
554
  // Clear input state and get back to normal mode.
@@ -575,6 +585,9 @@
575
585
  this.handleKey(cm, command.toKeys[i]);
576
586
  }
577
587
  } else {
588
+ if (macroModeState.enteredMacroMode) {
589
+ logKey(macroModeState, key);
590
+ }
578
591
  commandDispatcher.processCommand(cm, vim, command);
579
592
  }
580
593
  }
@@ -656,10 +669,13 @@
656
669
  */
657
670
  function RegisterController(registers) {
658
671
  this.registers = registers;
659
- this.unamedRegister = registers['\"'] = new Register();
672
+ this.unamedRegister = registers['"'] = new Register();
660
673
  }
661
674
  RegisterController.prototype = {
662
675
  pushText: function(registerName, operator, text, linewise) {
676
+ if (linewise && text.charAt(0) == '\n') {
677
+ text = text.slice(1) + '\n';
678
+ }
663
679
  // Lowercase and uppercase registers refer to the same register.
664
680
  // Uppercase just means append.
665
681
  var register = this.isValidRegister(registerName) ?
@@ -739,32 +755,31 @@
739
755
  // stroke.
740
756
  inputState.keyBuffer.push(key);
741
757
  return null;
742
- } else {
743
- if (inputState.operator && command.type == 'action') {
744
- // Ignore matched action commands after an operator. Operators
745
- // only operate on motions. This check is really for text
746
- // objects since aW, a[ etcs conflicts with a.
747
- continue;
748
- }
749
- // Matches whole comand. Return the command.
750
- if (command.keys[keys.length - 1] == 'character') {
751
- inputState.selectedCharacter = keys[keys.length - 1];
752
- if(inputState.selectedCharacter.length>1){
753
- switch(inputState.selectedCharacter){
754
- case "<CR>":
755
- inputState.selectedCharacter='\n';
756
- break;
757
- case "<Space>":
758
- inputState.selectedCharacter=' ';
759
- break;
760
- default:
761
- continue;
762
- }
758
+ }
759
+ if (inputState.operator && command.type == 'action') {
760
+ // Ignore matched action commands after an operator. Operators
761
+ // only operate on motions. This check is really for text
762
+ // objects since aW, a[ etcs conflicts with a.
763
+ continue;
764
+ }
765
+ // Matches whole comand. Return the command.
766
+ if (command.keys[keys.length - 1] == 'character') {
767
+ inputState.selectedCharacter = keys[keys.length - 1];
768
+ if(inputState.selectedCharacter.length>1){
769
+ switch(inputState.selectedCharacter){
770
+ case '<CR>':
771
+ inputState.selectedCharacter='\n';
772
+ break;
773
+ case '<Space>':
774
+ inputState.selectedCharacter=' ';
775
+ break;
776
+ default:
777
+ continue;
763
778
  }
764
779
  }
765
- inputState.keyBuffer = [];
766
- return command;
767
780
  }
781
+ inputState.keyBuffer = [];
782
+ return command;
768
783
  }
769
784
  }
770
785
  // Clear the buffer since there are no partial matches.
@@ -860,7 +875,10 @@
860
875
  actionArgs.repeatIsExplicit = repeatIsExplicit;
861
876
  actionArgs.registerName = inputState.registerName;
862
877
  vim.inputState = new InputState();
863
- vim.lastMotion = null,
878
+ vim.lastMotion = null;
879
+ if (command.isEdit) {
880
+ this.recordLastEdit(vim, inputState, command);
881
+ }
864
882
  actions[command.action](cm, actionArgs, vim);
865
883
  },
866
884
  processSearch: function(cm, vim, command) {
@@ -890,11 +908,11 @@
890
908
  cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
891
909
  handleQuery(query, true /** ignoreCase */, true /** smartCase */);
892
910
  }
893
- function onPromptKeyUp(e, query) {
911
+ function onPromptKeyUp(_e, query) {
894
912
  var parsedQuery;
895
913
  try {
896
914
  parsedQuery = updateSearchQuery(cm, query,
897
- true /** ignoreCase */, true /** smartCase */)
915
+ true /** ignoreCase */, true /** smartCase */);
898
916
  } catch (e) {
899
917
  // Swallow bad regexes for incremental search.
900
918
  }
@@ -905,7 +923,7 @@
905
923
  cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
906
924
  }
907
925
  }
908
- function onPromptKeyDown(e, query, close) {
926
+ function onPromptKeyDown(e, _query, close) {
909
927
  var keyName = CodeMirror.keyName(e);
910
928
  if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
911
929
  updateSearchQuery(cm, originalQuery);
@@ -961,9 +979,11 @@
961
979
  },
962
980
  processEx: function(cm, vim, command) {
963
981
  function onPromptClose(input) {
982
+ // Give the prompt some time to close so that if processCommand shows
983
+ // an error, the elements don't overlap.
964
984
  exCommandDispatcher.processCommand(cm, input);
965
985
  }
966
- function onPromptKeyDown(e, input, close) {
986
+ function onPromptKeyDown(e, _input, close) {
967
987
  var keyName = CodeMirror.keyName(e);
968
988
  if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
969
989
  CodeMirror.e_stop(e);
@@ -1002,7 +1022,7 @@
1002
1022
  var curEnd;
1003
1023
  var repeat;
1004
1024
  if (operator) {
1005
- this.recordLastEdit(cm, vim, inputState);
1025
+ this.recordLastEdit(vim, inputState);
1006
1026
  }
1007
1027
  if (inputState.repeatOverride !== undefined) {
1008
1028
  // If repeatOverride is specified, that takes precedence over the
@@ -1129,12 +1149,17 @@
1129
1149
  exitVisualMode(cm, vim);
1130
1150
  }
1131
1151
  if (operatorArgs.enterInsertMode) {
1132
- actions.enterInsertMode(cm);
1152
+ actions.enterInsertMode(cm, {}, vim);
1133
1153
  }
1134
1154
  }
1135
1155
  },
1136
- recordLastEdit: function(cm, vim, inputState) {
1137
- vim.lastEdit = inputState;
1156
+ recordLastEdit: function(vim, inputState, actionCommand) {
1157
+ var macroModeState = getVimGlobalState().macroModeState;
1158
+ if (macroModeState.inReplay) { return; }
1159
+ vim.lastEditInputState = inputState;
1160
+ vim.lastEditActionCommand = actionCommand;
1161
+ macroModeState.lastInsertModeChanges.changes = [];
1162
+ macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
1138
1163
  }
1139
1164
  };
1140
1165
 
@@ -1163,7 +1188,7 @@
1163
1188
  var cur = cm.getCursor();
1164
1189
  return { line: cur.line + motionArgs.repeat - 1, ch: Infinity };
1165
1190
  },
1166
- findNext: function(cm, motionArgs, vim) {
1191
+ findNext: function(cm, motionArgs) {
1167
1192
  var state = getSearchState(cm);
1168
1193
  var query = state.getQuery();
1169
1194
  if (!query) {
@@ -1175,7 +1200,7 @@
1175
1200
  highlightSearchMatches(cm, query);
1176
1201
  return findNext(cm, prev/** prev */, query, motionArgs.repeat);
1177
1202
  },
1178
- goToMark: function(cm, motionArgs, vim) {
1203
+ goToMark: function(_cm, motionArgs, vim) {
1179
1204
  var mark = vim.marks[motionArgs.selectedCharacter];
1180
1205
  if (mark) {
1181
1206
  return mark.find();
@@ -1183,7 +1208,7 @@
1183
1208
  return null;
1184
1209
  },
1185
1210
  jumpToMark: function(cm, motionArgs, vim) {
1186
- var best = cm.getCursor();
1211
+ var best = cm.getCursor();
1187
1212
  for (var i = 0; i < motionArgs.repeat; i++) {
1188
1213
  var cursor = best;
1189
1214
  for (var key in vim.marks) {
@@ -1192,7 +1217,7 @@
1192
1217
  }
1193
1218
  var mark = vim.marks[key].find();
1194
1219
  var isWrongDirection = (motionArgs.forward) ?
1195
- cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark)
1220
+ cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);
1196
1221
 
1197
1222
  if (isWrongDirection) {
1198
1223
  continue;
@@ -1254,7 +1279,7 @@
1254
1279
  endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
1255
1280
  vim.lastHPos = endCh;
1256
1281
  }
1257
- vim.lastHSPos = cm.charCoords({line:line, ch:endCh},"div").left;
1282
+ vim.lastHSPos = cm.charCoords({line:line, ch:endCh},'div').left;
1258
1283
  return { line: line, ch: endCh };
1259
1284
  },
1260
1285
  moveByDisplayLines: function(cm, motionArgs, vim) {
@@ -1267,10 +1292,10 @@
1267
1292
  case this.moveToEol:
1268
1293
  break;
1269
1294
  default:
1270
- vim.lastHSPos = cm.charCoords(cur,"div").left;
1295
+ vim.lastHSPos = cm.charCoords(cur,'div').left;
1271
1296
  }
1272
1297
  var repeat = motionArgs.repeat;
1273
- var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),"line",vim.lastHSPos);
1298
+ var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);
1274
1299
  if (res.hitSide) {
1275
1300
  if (motionArgs.forward) {
1276
1301
  var lastCharCoords = cm.charCoords(res, 'div');
@@ -1313,7 +1338,6 @@
1313
1338
  return { line: line, ch: 0 };
1314
1339
  },
1315
1340
  moveByScroll: function(cm, motionArgs, vim) {
1316
- var globalState = getVimGlobalState();
1317
1341
  var scrollbox = cm.getScrollInfo();
1318
1342
  var curEnd = null;
1319
1343
  var repeat = motionArgs.repeat;
@@ -1359,16 +1383,16 @@
1359
1383
  var repeat = motionArgs.repeat;
1360
1384
  // repeat is equivalent to which column we want to move to!
1361
1385
  vim.lastHPos = repeat - 1;
1362
- vim.lastHSPos = cm.charCoords(cm.getCursor(),"div").left;
1386
+ vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left;
1363
1387
  return moveToColumn(cm, repeat);
1364
1388
  },
1365
1389
  moveToEol: function(cm, motionArgs, vim) {
1366
1390
  var cur = cm.getCursor();
1367
1391
  vim.lastHPos = Infinity;
1368
- var retval={ line: cur.line + motionArgs.repeat - 1, ch: Infinity }
1392
+ var retval={ line: cur.line + motionArgs.repeat - 1, ch: Infinity };
1369
1393
  var end=cm.clipPos(retval);
1370
1394
  end.ch--;
1371
- vim.lastHSPos = cm.charCoords(end,"div").left;
1395
+ vim.lastHSPos = cm.charCoords(end,'div').left;
1372
1396
  return retval;
1373
1397
  },
1374
1398
  moveToFirstNonWhiteSpaceCharacter: function(cm) {
@@ -1378,7 +1402,7 @@
1378
1402
  return { line: cursor.line,
1379
1403
  ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) };
1380
1404
  },
1381
- moveToMatchedSymbol: function(cm, motionArgs) {
1405
+ moveToMatchedSymbol: function(cm) {
1382
1406
  var cursor = cm.getCursor();
1383
1407
  var line = cursor.line;
1384
1408
  var ch = cursor.ch;
@@ -1440,7 +1464,7 @@
1440
1464
  motionArgs.inclusive = forward ? true : false;
1441
1465
  var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
1442
1466
  if (!curEnd) {
1443
- cm.moveH(increment, 'char')
1467
+ cm.moveH(increment, 'char');
1444
1468
  return cm.getCursor();
1445
1469
  }
1446
1470
  curEnd.ch += increment;
@@ -1449,7 +1473,7 @@
1449
1473
  };
1450
1474
 
1451
1475
  var operators = {
1452
- change: function(cm, operatorArgs, vim, curStart, curEnd) {
1476
+ change: function(cm, operatorArgs, _vim, curStart, curEnd) {
1453
1477
  getVimGlobalState().registerController.pushText(
1454
1478
  operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd),
1455
1479
  operatorArgs.linewise);
@@ -1467,12 +1491,27 @@
1467
1491
  // curEnd should be on the first character of the new line.
1468
1492
  cm.replaceRange('\n', curStart, curEnd);
1469
1493
  } else {
1494
+ // Exclude trailing whitespace if the range is not all whitespace.
1495
+ var text = cm.getRange(curStart, curEnd);
1496
+ if (!isWhiteSpaceString(text)) {
1497
+ var match = (/\s+$/).exec(text);
1498
+ if (match) {
1499
+ curEnd = offsetCursor(curEnd, 0, - match[0].length);
1500
+ }
1501
+ }
1470
1502
  cm.replaceRange('', curStart, curEnd);
1471
1503
  }
1472
1504
  cm.setCursor(curStart);
1473
1505
  },
1474
1506
  // delete is a javascript keyword.
1475
- 'delete': function(cm, operatorArgs, vim, curStart, curEnd) {
1507
+ 'delete': function(cm, operatorArgs, _vim, curStart, curEnd) {
1508
+ // If the ending line is past the last line, inclusive, instead of
1509
+ // including the trailing \n, include the \n before the starting line
1510
+ if (operatorArgs.linewise &&
1511
+ curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) {
1512
+ curStart.line--;
1513
+ curStart.ch = lineLength(cm, curStart.line);
1514
+ }
1476
1515
  getVimGlobalState().registerController.pushText(
1477
1516
  operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd),
1478
1517
  operatorArgs.linewise);
@@ -1503,7 +1542,7 @@
1503
1542
  cm.setCursor(curStart);
1504
1543
  cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1505
1544
  },
1506
- swapcase: function(cm, operatorArgs, vim, curStart, curEnd, curOriginal) {
1545
+ swapcase: function(cm, _operatorArgs, _vim, curStart, curEnd, curOriginal) {
1507
1546
  var toSwap = cm.getRange(curStart, curEnd);
1508
1547
  var swapped = '';
1509
1548
  for (var i = 0; i < toSwap.length; i++) {
@@ -1514,7 +1553,7 @@
1514
1553
  cm.replaceRange(swapped, curStart, curEnd);
1515
1554
  cm.setCursor(curOriginal);
1516
1555
  },
1517
- yank: function(cm, operatorArgs, vim, curStart, curEnd, curOriginal) {
1556
+ yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
1518
1557
  getVimGlobalState().registerController.pushText(
1519
1558
  operatorArgs.registerName, 'yank',
1520
1559
  cm.getRange(curStart, curEnd), operatorArgs.linewise);
@@ -1538,7 +1577,7 @@
1538
1577
  },
1539
1578
  scrollToCursor: function(cm, actionArgs) {
1540
1579
  var lineNum = cm.getCursor().line;
1541
- var charCoords = cm.charCoords({line: lineNum, ch: 0}, "local");
1580
+ var charCoords = cm.charCoords({line: lineNum, ch: 0}, 'local');
1542
1581
  var height = cm.getScrollInfo().clientHeight;
1543
1582
  var y = charCoords.top;
1544
1583
  var lineHeight = charCoords.bottom - y;
@@ -1564,7 +1603,7 @@
1564
1603
  executeMacroKeyBuffer(cm, macroModeState, keyBuffer);
1565
1604
  }
1566
1605
  },
1567
- exitMacroRecordMode: function(cm, actionArgs) {
1606
+ exitMacroRecordMode: function() {
1568
1607
  var macroModeState = getVimGlobalState().macroModeState;
1569
1608
  macroModeState.toggle();
1570
1609
  parseKeyBufferToRegister(macroModeState.latestRegister,
@@ -1576,7 +1615,9 @@
1576
1615
  macroModeState.toggle(cm, registerName);
1577
1616
  emptyMacroKeyBuffer(macroModeState);
1578
1617
  },
1579
- enterInsertMode: function(cm, actionArgs) {
1618
+ enterInsertMode: function(cm, actionArgs, vim) {
1619
+ vim.insertMode = true;
1620
+ vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
1580
1621
  var insertAt = (actionArgs) ? actionArgs.insertAt : null;
1581
1622
  if (insertAt == 'eol') {
1582
1623
  var cursor = cm.getCursor();
@@ -1588,6 +1629,19 @@
1588
1629
  cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1589
1630
  }
1590
1631
  cm.setOption('keyMap', 'vim-insert');
1632
+ if (actionArgs && actionArgs.replace) {
1633
+ // Handle Replace-mode as a special case of insert mode.
1634
+ cm.toggleOverwrite(true);
1635
+ cm.setOption('keyMap', 'vim-replace');
1636
+ } else {
1637
+ cm.setOption('keyMap', 'vim-insert');
1638
+ }
1639
+ if (!getVimGlobalState().macroModeState.inReplay) {
1640
+ // Only record if not replaying.
1641
+ cm.on('change', onChange);
1642
+ cm.on('cursorActivity', onCursorActivity);
1643
+ CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
1644
+ }
1591
1645
  },
1592
1646
  toggleVisualMode: function(cm, actionArgs, vim) {
1593
1647
  var repeat = actionArgs.repeat;
@@ -1673,7 +1727,7 @@
1673
1727
  cm.setCursor(curFinalPos);
1674
1728
  });
1675
1729
  },
1676
- newLineAndEnterInsertMode: function(cm, actionArgs) {
1730
+ newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
1677
1731
  var insertAt = cm.getCursor();
1678
1732
  if (insertAt.line === cm.firstLine() && !actionArgs.after) {
1679
1733
  // Special case for inserting newline before start of document.
@@ -1688,9 +1742,9 @@
1688
1742
  CodeMirror.commands.newlineAndIndent;
1689
1743
  newlineFn(cm);
1690
1744
  }
1691
- this.enterInsertMode(cm);
1745
+ this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);
1692
1746
  },
1693
- paste: function(cm, actionArgs, vim) {
1747
+ paste: function(cm, actionArgs) {
1694
1748
  var cur = cm.getCursor();
1695
1749
  var register = getVimGlobalState().registerController.getRegister(
1696
1750
  actionArgs.registerName);
@@ -1733,12 +1787,15 @@
1733
1787
  cm.setCursor(curPosFinal);
1734
1788
  },
1735
1789
  undo: function(cm, actionArgs) {
1736
- repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
1790
+ cm.operation(function() {
1791
+ repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
1792
+ cm.setCursor(cm.getCursor('anchor'));
1793
+ });
1737
1794
  },
1738
1795
  redo: function(cm, actionArgs) {
1739
1796
  repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
1740
1797
  },
1741
- setRegister: function(cm, actionArgs, vim) {
1798
+ setRegister: function(_cm, actionArgs, vim) {
1742
1799
  vim.inputState.registerName = actionArgs.selectedCharacter;
1743
1800
  },
1744
1801
  setMark: function(cm, actionArgs, vim) {
@@ -1781,11 +1838,7 @@
1781
1838
  }
1782
1839
  }
1783
1840
  },
1784
- enterReplaceMode: function(cm, actionArgs) {
1785
- cm.setOption('keyMap', 'vim-replace');
1786
- cm.toggleOverwrite();
1787
- },
1788
- incrementNumberToken: function(cm, actionArgs, vim) {
1841
+ incrementNumberToken: function(cm, actionArgs) {
1789
1842
  var cur = cm.getCursor();
1790
1843
  var lineStr = cm.getLine(cur.line);
1791
1844
  var re = /-?\d+/g;
@@ -1814,17 +1867,15 @@
1814
1867
  cm.setCursor({line: cur.line, ch: start + numberStr.length - 1});
1815
1868
  },
1816
1869
  repeatLastEdit: function(cm, actionArgs, vim) {
1817
- // TODO: Make this repeat insert mode changes.
1818
- var lastEdit = vim.lastEdit;
1819
- if (lastEdit) {
1820
- if (actionArgs.repeat && actionArgs.repeatIsExplicit) {
1821
- vim.lastEdit.repeatOverride = actionArgs.repeat;
1822
- }
1823
- var currentInputState = vim.inputState;
1824
- vim.inputState = vim.lastEdit;
1825
- commandDispatcher.evalInput(cm, vim);
1826
- vim.inputState = currentInputState;
1870
+ var lastEditInputState = vim.lastEditInputState;
1871
+ if (!lastEditInputState) { return; }
1872
+ var repeat = actionArgs.repeat;
1873
+ if (repeat && actionArgs.repeatIsExplicit) {
1874
+ vim.lastEditInputState.repeatOverride = repeat;
1875
+ } else {
1876
+ repeat = vim.lastEditInputState.repeatOverride || repeat;
1827
1877
  }
1878
+ repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
1828
1879
  }
1829
1880
  };
1830
1881
 
@@ -1853,7 +1904,7 @@
1853
1904
  '\'': function(cm, inclusive) {
1854
1905
  return findBeginningAndEnd(cm, "'", inclusive);
1855
1906
  },
1856
- '\"': function(cm, inclusive) {
1907
+ '"': function(cm, inclusive) {
1857
1908
  return findBeginningAndEnd(cm, '"', inclusive);
1858
1909
  }
1859
1910
  };
@@ -1873,14 +1924,6 @@
1873
1924
  var ch = Math.min(Math.max(0, cur.ch), maxCh);
1874
1925
  return { line: line, ch: ch };
1875
1926
  }
1876
- // Merge arguments in place, for overriding arguments.
1877
- function mergeArgs(to, from) {
1878
- for (var prop in from) {
1879
- if (from.hasOwnProperty(prop)) {
1880
- to[prop] = from[prop];
1881
- }
1882
- }
1883
- }
1884
1927
  function copyArgs(args) {
1885
1928
  var ret = {};
1886
1929
  for (var prop in args) {
@@ -1893,17 +1936,6 @@
1893
1936
  function offsetCursor(cur, offsetLine, offsetCh) {
1894
1937
  return { line: cur.line + offsetLine, ch: cur.ch + offsetCh };
1895
1938
  }
1896
- function arrayEq(a1, a2) {
1897
- if (a1.length != a2.length) {
1898
- return false;
1899
- }
1900
- for (var i = 0; i < a1.length; i++) {
1901
- if (a1[i] != a2[i]) {
1902
- return false;
1903
- }
1904
- }
1905
- return true;
1906
- }
1907
1939
  function matchKeysPartial(pressed, mapped) {
1908
1940
  for (var i = 0; i < pressed.length; i++) {
1909
1941
  // 'character' means any character. For mark, register commads, etc.
@@ -1913,14 +1945,6 @@
1913
1945
  }
1914
1946
  return true;
1915
1947
  }
1916
- function arrayIsSubsetFromBeginning(small, big) {
1917
- for (var i = 0; i < small.length; i++) {
1918
- if (small[i] != big[i]) {
1919
- return false;
1920
- }
1921
- }
1922
- return true;
1923
- }
1924
1948
  function repeatFn(cm, fn, repeat) {
1925
1949
  return function() {
1926
1950
  for (var i = 0; i < repeat; i++) {
@@ -1937,7 +1961,8 @@
1937
1961
  function cursorIsBefore(cur1, cur2) {
1938
1962
  if (cur1.line < cur2.line) {
1939
1963
  return true;
1940
- } else if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
1964
+ }
1965
+ if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
1941
1966
  return true;
1942
1967
  }
1943
1968
  return false;
@@ -1952,17 +1977,16 @@
1952
1977
  return cm.getLine(lineNum).length;
1953
1978
  }
1954
1979
  function reverse(s){
1955
- return s.split("").reverse().join("");
1980
+ return s.split('').reverse().join('');
1956
1981
  }
1957
1982
  function trim(s) {
1958
1983
  if (s.trim) {
1959
1984
  return s.trim();
1960
- } else {
1961
- return s.replace(/^\s+|\s+$/g, '');
1962
1985
  }
1986
+ return s.replace(/^\s+|\s+$/g, '');
1963
1987
  }
1964
1988
  function escapeRegex(s) {
1965
- return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, "\\$1");
1989
+ return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
1966
1990
  }
1967
1991
 
1968
1992
  function exitVisualMode(cm, vim) {
@@ -1997,7 +2021,6 @@
1997
2021
  // Find the line containing the last word, and clip all whitespace up
1998
2022
  // to it.
1999
2023
  for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
2000
- var clipped = false;
2001
2024
  curEnd.line--;
2002
2025
  curEnd.ch = 0;
2003
2026
  }
@@ -2012,7 +2035,7 @@
2012
2035
  }
2013
2036
 
2014
2037
  // Expand the selection to line ends.
2015
- function expandSelectionToLine(cm, curStart, curEnd) {
2038
+ function expandSelectionToLine(_cm, curStart, curEnd) {
2016
2039
  curStart.ch = 0;
2017
2040
  curEnd.ch = 0;
2018
2041
  curEnd.line++;
@@ -2026,7 +2049,7 @@
2026
2049
  return firstNonWS == -1 ? text.length : firstNonWS;
2027
2050
  }
2028
2051
 
2029
- function expandWordUnderCursor(cm, inclusive, forward, bigWord, noSymbol) {
2052
+ function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
2030
2053
  var cur = cm.getCursor();
2031
2054
  var line = cm.getLine(cur.line);
2032
2055
  var idx = cur.ch;
@@ -2137,7 +2160,7 @@
2137
2160
  }
2138
2161
  },
2139
2162
  // TODO: The original Vim implementation only operates on level 1 and 2.
2140
- // The current implementation doesn't check for code block level and
2163
+ // The current implementation doesn't check for code block level and
2141
2164
  // therefore it operates on any levels.
2142
2165
  method: {
2143
2166
  init: function(state) {
@@ -2189,7 +2212,7 @@
2189
2212
  reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
2190
2213
  forward: forward,
2191
2214
  depth: 0,
2192
- curMoveThrough: false
2215
+ curMoveThrough: false
2193
2216
  };
2194
2217
  var mode = symbolToMode[symb];
2195
2218
  if(!mode)return cur;
@@ -2298,7 +2321,7 @@
2298
2321
  pos = (dir > 0) ? 0 : line.length;
2299
2322
  }
2300
2323
  // Should never get here.
2301
- throw 'The impossible happened.';
2324
+ throw new Error('The impossible happened.');
2302
2325
  }
2303
2326
 
2304
2327
  /**
@@ -2484,16 +2507,6 @@
2484
2507
  return { start: start, end: end };
2485
2508
  }
2486
2509
 
2487
- function regexLastIndexOf(string, pattern, startIndex) {
2488
- for (var i = !startIndex ? string.length : startIndex;
2489
- i >= 0; --i) {
2490
- if (pattern.test(string.charAt(i))) {
2491
- return i;
2492
- }
2493
- }
2494
- return -1;
2495
- }
2496
-
2497
2510
  // Takes in a symbol and a cursor and tries to simulate text objects that
2498
2511
  // have identical opening and closing symbols
2499
2512
  // TODO support across multiple lines
@@ -2587,9 +2600,10 @@
2587
2600
  onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp });
2588
2601
  }
2589
2602
  else {
2590
- onClose(prompt(shortText, ""));
2603
+ onClose(prompt(shortText, ''));
2591
2604
  }
2592
2605
  }
2606
+
2593
2607
  function findUnescapedSlashes(str) {
2594
2608
  var escapeNextChar = false;
2595
2609
  var slashes = [];
@@ -2612,7 +2626,7 @@
2612
2626
  * then both ignoreCase and smartCase are ignored, and 'i' will be passed
2613
2627
  * through to the Regex object.
2614
2628
  */
2615
- function parseQuery(cm, query, ignoreCase, smartCase) {
2629
+ function parseQuery(query, ignoreCase, smartCase) {
2616
2630
  // Check if the query is already a regex.
2617
2631
  if (query instanceof RegExp) { return query; }
2618
2632
  // First try to extract regex + flags from the input. If no flags found,
@@ -2671,16 +2685,16 @@
2671
2685
  }
2672
2686
  function regexEqual(r1, r2) {
2673
2687
  if (r1 instanceof RegExp && r2 instanceof RegExp) {
2674
- var props = ["global", "multiline", "ignoreCase", "source"];
2688
+ var props = ['global', 'multiline', 'ignoreCase', 'source'];
2675
2689
  for (var i = 0; i < props.length; i++) {
2676
2690
  var prop = props[i];
2677
2691
  if (r1[prop] !== r2[prop]) {
2678
- return(false);
2692
+ return false;
2679
2693
  }
2680
2694
  }
2681
- return(true);
2695
+ return true;
2682
2696
  }
2683
- return(false);
2697
+ return false;
2684
2698
  }
2685
2699
  // Returns true if the query is valid.
2686
2700
  function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
@@ -2688,7 +2702,7 @@
2688
2702
  return;
2689
2703
  }
2690
2704
  var state = getSearchState(cm);
2691
- var query = parseQuery(cm, rawQuery, !!ignoreCase, !!smartCase);
2705
+ var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);
2692
2706
  if (!query) {
2693
2707
  return;
2694
2708
  }
@@ -2714,7 +2728,7 @@
2714
2728
  if (match[0].length == 0) {
2715
2729
  // Matched empty string, skip to next.
2716
2730
  stream.next();
2717
- return "searching";
2731
+ return 'searching';
2718
2732
  }
2719
2733
  if (!stream.sol()) {
2720
2734
  // Backtrack 1 to match \b
@@ -2725,7 +2739,7 @@
2725
2739
  }
2726
2740
  }
2727
2741
  stream.match(query);
2728
- return "searching";
2742
+ return 'searching';
2729
2743
  }
2730
2744
  while (!stream.eol()) {
2731
2745
  stream.next();
@@ -2765,7 +2779,8 @@
2765
2779
  }
2766
2780
  }
2767
2781
  return cursor.from();
2768
- });}
2782
+ });
2783
+ }
2769
2784
  function clearSearchHighlight(cm) {
2770
2785
  cm.removeOverlay(getSearchState(cm).getOverlay());
2771
2786
  getSearchState(cm).setOverlay(null);
@@ -2798,10 +2813,10 @@
2798
2813
  }
2799
2814
  function getUserVisibleLines(cm) {
2800
2815
  var scrollInfo = cm.getScrollInfo();
2801
- var occludeTorleranceTop = 6;
2802
- var occludeTorleranceBottom = 10;
2803
- var from = cm.coordsChar({left:0, top: occludeTorleranceTop}, 'local');
2804
- var bottomY = scrollInfo.clientHeight - occludeTorleranceBottom;
2816
+ var occludeToleranceTop = 6;
2817
+ var occludeToleranceBottom = 10;
2818
+ var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
2819
+ var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
2805
2820
  var to = cm.coordsChar({left:0, top: bottomY}, 'local');
2806
2821
  return {top: from.line, bottom: to.line};
2807
2822
  }
@@ -2815,6 +2830,7 @@
2815
2830
  { name: 'write', shortName: 'w', type: 'builtIn' },
2816
2831
  { name: 'undo', shortName: 'u', type: 'builtIn' },
2817
2832
  { name: 'redo', shortName: 'red', type: 'builtIn' },
2833
+ { name: 'sort', shortName: 'sor', type: 'builtIn'},
2818
2834
  { name: 'substitute', shortName: 's', type: 'builtIn'},
2819
2835
  { name: 'nohlsearch', shortName: 'noh', type: 'builtIn'},
2820
2836
  { name: 'delmarks', shortName: 'delm', type: 'builtin'}
@@ -2824,6 +2840,10 @@
2824
2840
  };
2825
2841
  Vim.ExCommandDispatcher.prototype = {
2826
2842
  processCommand: function(cm, input) {
2843
+ var vim = getVimState(cm);
2844
+ if (vim.visualMode) {
2845
+ exitVisualMode(cm, vim);
2846
+ }
2827
2847
  var inputStream = new CodeMirror.StringStream(input);
2828
2848
  var params = {};
2829
2849
  params.input = input;
@@ -2847,7 +2867,7 @@
2847
2867
  if (command.type == 'exToKey') {
2848
2868
  // Handle Ex to Key mapping.
2849
2869
  for (var i = 0; i < command.toKeys.length; i++) {
2850
- vim.handleKey(cm, command.toKeys[i]);
2870
+ CodeMirror.Vim.handleKey(cm, command.toKeys[i]);
2851
2871
  }
2852
2872
  return;
2853
2873
  } else if (command.type == 'exToEx') {
@@ -2861,7 +2881,11 @@
2861
2881
  showConfirm(cm, 'Not an editor command ":' + input + '"');
2862
2882
  return;
2863
2883
  }
2864
- exCommands[commandName](cm, params);
2884
+ try {
2885
+ exCommands[commandName](cm, params);
2886
+ } catch(e) {
2887
+ showConfirm(cm, e);
2888
+ }
2865
2889
  },
2866
2890
  parseInput_: function(cm, inputStream, result) {
2867
2891
  inputStream.eatWhile(':');
@@ -2900,13 +2924,11 @@
2900
2924
  var mark = getVimState(cm).marks[inputStream.next()];
2901
2925
  if (mark && mark.find()) {
2902
2926
  return mark.find().line;
2903
- } else {
2904
- throw "Mark not set";
2905
2927
  }
2906
- break;
2928
+ throw new Error('Mark not set');
2907
2929
  default:
2908
2930
  inputStream.backUp(1);
2909
- return cm.getCursor().line;
2931
+ return undefined;
2910
2932
  }
2911
2933
  },
2912
2934
  parseCommandArgs_: function(inputStream, params, command) {
@@ -3016,7 +3038,82 @@
3016
3038
  linewise: true },
3017
3039
  repeatOverride: params.line+1});
3018
3040
  },
3041
+ sort: function(cm, params) {
3042
+ var reverse, ignoreCase, unique, number;
3043
+ function parseArgs() {
3044
+ if (params.argString) {
3045
+ var args = new CodeMirror.StringStream(params.argString);
3046
+ if (args.eat('!')) { reverse = true; }
3047
+ if (args.eol()) { return; }
3048
+ if (!args.eatSpace()) { throw new Error('invalid arguments ' + args.match(/.*/)[0]); }
3049
+ var opts = args.match(/[a-z]+/);
3050
+ if (opts) {
3051
+ opts = opts[0];
3052
+ ignoreCase = opts.indexOf('i') != -1;
3053
+ unique = opts.indexOf('u') != -1;
3054
+ var decimal = opts.indexOf('d') != -1 && 1;
3055
+ var hex = opts.indexOf('x') != -1 && 1;
3056
+ var octal = opts.indexOf('o') != -1 && 1;
3057
+ if (decimal + hex + octal > 1) { throw new Error('invalid arguments'); }
3058
+ number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
3059
+ }
3060
+ if (args.eatSpace() && args.match(/\/.*\//)) { throw new Error('patterns not supported'); }
3061
+ }
3062
+ }
3063
+ parseArgs();
3064
+ var lineStart = params.line || cm.firstLine();
3065
+ var lineEnd = params.lineEnd || params.line || cm.lastLine();
3066
+ if (lineStart == lineEnd) { return; }
3067
+ var curStart = { line: lineStart, ch: 0 };
3068
+ var curEnd = { line: lineEnd, ch: lineLength(cm, lineEnd) };
3069
+ var text = cm.getRange(curStart, curEnd).split('\n');
3070
+ var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ :
3071
+ (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :
3072
+ (number == 'octal') ? /([0-7]+)/ : null;
3073
+ var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;
3074
+ var numPart = [], textPart = [];
3075
+ if (number) {
3076
+ for (var i = 0; i < text.length; i++) {
3077
+ if (numberRegex.exec(text[i])) {
3078
+ numPart.push(text[i]);
3079
+ } else {
3080
+ textPart.push(text[i]);
3081
+ }
3082
+ }
3083
+ } else {
3084
+ textPart = text;
3085
+ }
3086
+ function compareFn(a, b) {
3087
+ if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
3088
+ if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }
3089
+ var anum = number && numberRegex.exec(a);
3090
+ var bnum = number && numberRegex.exec(b);
3091
+ if (!anum) { return a < b ? -1 : 1; }
3092
+ anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);
3093
+ bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);
3094
+ return anum - bnum;
3095
+ }
3096
+ numPart.sort(compareFn);
3097
+ textPart.sort(compareFn);
3098
+ text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);
3099
+ if (unique) { // Remove duplicate lines
3100
+ var textOld = text;
3101
+ var lastLine;
3102
+ text = [];
3103
+ for (var i = 0; i < textOld.length; i++) {
3104
+ if (textOld[i] != lastLine) {
3105
+ text.push(textOld[i]);
3106
+ }
3107
+ lastLine = textOld[i];
3108
+ }
3109
+ }
3110
+ cm.replaceRange(text.join('\n'), curStart, curEnd);
3111
+ },
3019
3112
  substitute: function(cm, params) {
3113
+ if (!cm.getSearchCursor) {
3114
+ throw new Error('Search feature not available. Requires searchcursor.js or ' +
3115
+ 'any other getSearchCursor implementation.');
3116
+ }
3020
3117
  var argString = params.argString;
3021
3118
  var slashes = findUnescapedSlashes(argString);
3022
3119
  if (slashes[0] !== 0) {
@@ -3028,6 +3125,7 @@
3028
3125
  var replacePart = '';
3029
3126
  var flagsPart;
3030
3127
  var count;
3128
+ var confirm = false; // Whether to confirm each replace.
3031
3129
  if (slashes[1]) {
3032
3130
  replacePart = argString.substring(slashes[1] + 1, slashes[2]);
3033
3131
  }
@@ -3039,6 +3137,10 @@
3039
3137
  count = parseInt(trailing[1]);
3040
3138
  }
3041
3139
  if (flagsPart) {
3140
+ if (flagsPart.indexOf('c') != -1) {
3141
+ confirm = true;
3142
+ flagsPart.replace('c', '');
3143
+ }
3042
3144
  regexPart = regexPart + '/' + flagsPart;
3043
3145
  }
3044
3146
  if (regexPart) {
@@ -3054,27 +3156,15 @@
3054
3156
  }
3055
3157
  var state = getSearchState(cm);
3056
3158
  var query = state.getQuery();
3057
- var lineStart = params.line || cm.firstLine();
3159
+ var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
3058
3160
  var lineEnd = params.lineEnd || lineStart;
3059
3161
  if (count) {
3060
3162
  lineStart = lineEnd;
3061
3163
  lineEnd = lineStart + count - 1;
3062
3164
  }
3063
3165
  var startPos = clipCursorToContent(cm, { line: lineStart, ch: 0 });
3064
- function doReplace() {
3065
- for (var cursor = cm.getSearchCursor(query, startPos);
3066
- cursor.findNext() &&
3067
- isInRange(cursor.from(), lineStart, lineEnd);) {
3068
- var text = cm.getRange(cursor.from(), cursor.to());
3069
- var newText = text.replace(query, replacePart);
3070
- cursor.replace(newText);
3071
- }
3072
- var vim = getVimState(cm);
3073
- if (vim.visualMode) {
3074
- exitVisualMode(cm, vim);
3075
- }
3076
- }
3077
- cm.operation(doReplace);
3166
+ var cursor = cm.getSearchCursor(query, startPos);
3167
+ doReplace(cm, confirm, lineStart, lineEnd, cursor, query, replacePart);
3078
3168
  },
3079
3169
  redo: CodeMirror.commands.redo,
3080
3170
  undo: CodeMirror.commands.undo,
@@ -3142,7 +3232,7 @@
3142
3232
  delete state.marks[mark];
3143
3233
  }
3144
3234
  } else {
3145
- showConfirm(cm, 'Invalid argument: ' + startMark + "-");
3235
+ showConfirm(cm, 'Invalid argument: ' + startMark + '-');
3146
3236
  return;
3147
3237
  }
3148
3238
  } else {
@@ -3155,6 +3245,95 @@
3155
3245
 
3156
3246
  var exCommandDispatcher = new Vim.ExCommandDispatcher();
3157
3247
 
3248
+ /**
3249
+ * @param {CodeMirror} cm CodeMirror instance we are in.
3250
+ * @param {boolean} confirm Whether to confirm each replace.
3251
+ * @param {Cursor} lineStart Line to start replacing from.
3252
+ * @param {Cursor} lineEnd Line to stop replacing at.
3253
+ * @param {RegExp} query Query for performing matches with.
3254
+ * @param {string} replaceWith Text to replace matches with. May contain $1,
3255
+ * $2, etc for replacing captured groups using Javascript replace.
3256
+ */
3257
+ function doReplace(cm, confirm, lineStart, lineEnd, searchCursor, query,
3258
+ replaceWith) {
3259
+ // Set up all the functions.
3260
+ var done = false;
3261
+ var lastPos = searchCursor.from();
3262
+ function replaceAll() {
3263
+ cm.operation(function() {
3264
+ while (!done) {
3265
+ replace();
3266
+ next();
3267
+ }
3268
+ stop();
3269
+ });
3270
+ }
3271
+ function replace() {
3272
+ var text = cm.getRange(searchCursor.from(), searchCursor.to());
3273
+ var newText = text.replace(query, replaceWith);
3274
+ searchCursor.replace(newText);
3275
+ }
3276
+ function next() {
3277
+ var found = searchCursor.findNext();
3278
+ if (!found) {
3279
+ done = true;
3280
+ } else if (isInRange(searchCursor.from(), lineStart, lineEnd)) {
3281
+ cm.scrollIntoView(searchCursor.from(), 30);
3282
+ cm.setSelection(searchCursor.from(), searchCursor.to());
3283
+ lastPos = searchCursor.from();
3284
+ done = false;
3285
+ } else {
3286
+ done = true;
3287
+ }
3288
+ }
3289
+ function stop(close) {
3290
+ if (close) { close(); }
3291
+ cm.focus();
3292
+ if (lastPos) {
3293
+ cm.setCursor(lastPos);
3294
+ var vim = getVimState(cm);
3295
+ vim.lastHPos = vim.lastHSPos = lastPos.ch;
3296
+ }
3297
+ }
3298
+ function onPromptKeyDown(e, _value, close) {
3299
+ // Swallow all keys.
3300
+ CodeMirror.e_stop(e);
3301
+ var keyName = CodeMirror.keyName(e);
3302
+ switch (keyName) {
3303
+ case 'Y':
3304
+ replace(); next(); break;
3305
+ case 'N':
3306
+ next(); break;
3307
+ case 'A':
3308
+ cm.operation(replaceAll); break;
3309
+ case 'L':
3310
+ replace();
3311
+ // fall through and exit.
3312
+ case 'Q':
3313
+ case 'Esc':
3314
+ case 'Ctrl-C':
3315
+ case 'Ctrl-[':
3316
+ stop(close);
3317
+ break;
3318
+ }
3319
+ if (done) { stop(close); }
3320
+ }
3321
+
3322
+ // Actually do replace.
3323
+ next();
3324
+ if (done) {
3325
+ throw new Error('No matches for ' + query.source);
3326
+ }
3327
+ if (!confirm) {
3328
+ replaceAll();
3329
+ return;
3330
+ }
3331
+ showPrompt(cm, {
3332
+ prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)',
3333
+ onKeyDown: onPromptKeyDown
3334
+ });
3335
+ }
3336
+
3158
3337
  // Register Vim with CodeMirror
3159
3338
  function buildVimKeyMap() {
3160
3339
  /**
@@ -3188,13 +3367,13 @@
3188
3367
  // Closure to bind CodeMirror, key, modifier.
3189
3368
  function keyMapper(vimKey) {
3190
3369
  return function(cm) {
3191
- vim.handleKey(cm, vimKey);
3370
+ CodeMirror.Vim.handleKey(cm, vimKey);
3192
3371
  };
3193
3372
  }
3194
3373
 
3195
- var modifiers = ['Shift', 'Ctrl'];
3196
3374
  var cmToVimKeymap = {
3197
3375
  'nofallthrough': true,
3376
+ 'disableInput': true,
3198
3377
  'style': 'fat-cursor'
3199
3378
  };
3200
3379
  function bindKeys(keys, modifier) {
@@ -3224,8 +3403,24 @@
3224
3403
  CodeMirror.keyMap.vim = buildVimKeyMap();
3225
3404
 
3226
3405
  function exitInsertMode(cm) {
3406
+ var vim = getVimState(cm);
3407
+ vim.insertMode = false;
3408
+ var inReplay = getVimGlobalState().macroModeState.inReplay;
3409
+ if (!inReplay) {
3410
+ cm.off('change', onChange);
3411
+ cm.off('cursorActivity', onCursorActivity);
3412
+ CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
3413
+ }
3414
+ if (!inReplay && vim.insertModeRepeat > 1) {
3415
+ // Perform insert mode repeat for commands like 3,a and 3,o.
3416
+ repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
3417
+ true /** repeatForInsert */);
3418
+ vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;
3419
+ }
3420
+ delete vim.insertModeRepeat;
3227
3421
  cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
3228
3422
  cm.setOption('keyMap', 'vim');
3423
+ cm.toggleOverwrite(false); // exit replace mode if we were in it.
3229
3424
  }
3230
3425
 
3231
3426
  CodeMirror.keyMap['vim-insert'] = {
@@ -3244,6 +3439,11 @@
3244
3439
  fallthrough: ['default']
3245
3440
  };
3246
3441
 
3442
+ CodeMirror.keyMap['vim-replace'] = {
3443
+ 'Backspace': 'goCharLeft',
3444
+ fallthrough: ['vim-insert']
3445
+ };
3446
+
3247
3447
  function parseRegisterToKeyBuffer(macroModeState, registerName) {
3248
3448
  var match, key;
3249
3449
  var register = getVimGlobalState().registerController.getRegister(registerName);
@@ -3251,7 +3451,7 @@
3251
3451
  var macroKeyBuffer = macroModeState.macroKeyBuffer;
3252
3452
  emptyMacroKeyBuffer(macroModeState);
3253
3453
  do {
3254
- match = text.match(/<\w+-.+>|<\w+>|.|\n/);
3454
+ match = (/<\w+-.+?>|<\w+>|./).exec(text);
3255
3455
  if(match === null)break;
3256
3456
  key = match[0];
3257
3457
  text = text.substring(match.index + key.length);
@@ -3285,24 +3485,145 @@
3285
3485
  macroKeyBuffer.push(key);
3286
3486
  }
3287
3487
 
3288
- function exitReplaceMode(cm) {
3289
- cm.toggleOverwrite();
3290
- cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
3291
- cm.setOption('keyMap', 'vim');
3488
+ /**
3489
+ * Listens for changes made in insert mode.
3490
+ * Should only be active in insert mode.
3491
+ */
3492
+ function onChange(_cm, changeObj) {
3493
+ var macroModeState = getVimGlobalState().macroModeState;
3494
+ var lastChange = macroModeState.lastInsertModeChanges;
3495
+ while (changeObj) {
3496
+ lastChange.expectCursorActivityForChange = true;
3497
+ if (changeObj.origin == '+input' || changeObj.origin == 'paste'
3498
+ || changeObj.origin === undefined /* only in testing */) {
3499
+ var text = changeObj.text.join('\n');
3500
+ lastChange.changes.push(text);
3501
+ }
3502
+ // Change objects may be chained with next.
3503
+ changeObj = changeObj.next;
3504
+ }
3292
3505
  }
3293
3506
 
3294
- CodeMirror.keyMap['vim-replace'] = {
3295
- 'Esc': exitReplaceMode,
3296
- 'Ctrl-[': exitReplaceMode,
3297
- 'Ctrl-C': exitReplaceMode,
3298
- 'Backspace': 'goCharLeft',
3299
- fallthrough: ['default']
3507
+ /**
3508
+ * Listens for any kind of cursor activity on CodeMirror.
3509
+ * - For tracking cursor activity in insert mode.
3510
+ * - Should only be active in insert mode.
3511
+ */
3512
+ function onCursorActivity() {
3513
+ var macroModeState = getVimGlobalState().macroModeState;
3514
+ var lastChange = macroModeState.lastInsertModeChanges;
3515
+ if (lastChange.expectCursorActivityForChange) {
3516
+ lastChange.expectCursorActivityForChange = false;
3517
+ } else {
3518
+ // Cursor moved outside the context of an edit. Reset the change.
3519
+ lastChange.changes = [];
3520
+ }
3521
+ }
3522
+
3523
+ /** Wrapper for special keys pressed in insert mode */
3524
+ function InsertModeKey(keyName) {
3525
+ this.keyName = keyName;
3526
+ }
3527
+
3528
+ /**
3529
+ * Handles raw key down events from the text area.
3530
+ * - Should only be active in insert mode.
3531
+ * - For recording deletes in insert mode.
3532
+ */
3533
+ function onKeyEventTargetKeyDown(e) {
3534
+ var macroModeState = getVimGlobalState().macroModeState;
3535
+ var lastChange = macroModeState.lastInsertModeChanges;
3536
+ var keyName = CodeMirror.keyName(e);
3537
+ function onKeyFound() {
3538
+ lastChange.changes.push(new InsertModeKey(keyName));
3539
+ return true;
3540
+ }
3541
+ if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
3542
+ CodeMirror.lookupKey(keyName, ['vim-insert'], onKeyFound);
3543
+ }
3544
+ }
3545
+
3546
+ /**
3547
+ * Repeats the last edit, which includes exactly 1 command and at most 1
3548
+ * insert. Operator and motion commands are read from lastEditInputState,
3549
+ * while action commands are read from lastEditActionCommand.
3550
+ *
3551
+ * If repeatForInsert is true, then the function was called by
3552
+ * exitInsertMode to repeat the insert mode changes the user just made. The
3553
+ * corresponding enterInsertMode call was made with a count.
3554
+ */
3555
+ function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
3556
+ var macroModeState = getVimGlobalState().macroModeState;
3557
+ macroModeState.inReplay = true;
3558
+ var isAction = !!vim.lastEditActionCommand;
3559
+ var cachedInputState = vim.inputState;
3560
+ function repeatCommand() {
3561
+ if (isAction) {
3562
+ commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);
3563
+ } else {
3564
+ commandDispatcher.evalInput(cm, vim);
3565
+ }
3566
+ }
3567
+ function repeatInsert(repeat) {
3568
+ if (macroModeState.lastInsertModeChanges.changes.length > 0) {
3569
+ // For some reason, repeat cw in desktop VIM will does not repeat
3570
+ // insert mode changes. Will conform to that behavior.
3571
+ repeat = !vim.lastEditActionCommand ? 1 : repeat;
3572
+ repeatLastInsertModeChanges(cm, repeat, macroModeState);
3573
+ }
3574
+ }
3575
+ vim.inputState = vim.lastEditInputState;
3576
+ if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {
3577
+ // o and O repeat have to be interlaced with insert repeats so that the
3578
+ // insertions appear on separate lines instead of the last line.
3579
+ for (var i = 0; i < repeat; i++) {
3580
+ repeatCommand();
3581
+ repeatInsert(1);
3582
+ }
3583
+ } else {
3584
+ if (!repeatForInsert) {
3585
+ // Hack to get the cursor to end up at the right place. If I is
3586
+ // repeated in insert mode repeat, cursor will be 1 insert
3587
+ // change set left of where it should be.
3588
+ repeatCommand();
3589
+ }
3590
+ repeatInsert(repeat);
3591
+ }
3592
+ vim.inputState = cachedInputState;
3593
+ if (vim.insertMode && !repeatForInsert) {
3594
+ // Don't exit insert mode twice. If repeatForInsert is set, then we
3595
+ // were called by an exitInsertMode call lower on the stack.
3596
+ exitInsertMode(cm);
3597
+ }
3598
+ macroModeState.inReplay = false;
3300
3599
  };
3301
3600
 
3601
+ function repeatLastInsertModeChanges(cm, repeat, macroModeState) {
3602
+ var lastChange = macroModeState.lastInsertModeChanges;
3603
+ function keyHandler(binding) {
3604
+ if (typeof binding == 'string') {
3605
+ CodeMirror.commands[binding](cm);
3606
+ } else {
3607
+ binding(cm);
3608
+ }
3609
+ return true;
3610
+ }
3611
+ for (var i = 0; i < repeat; i++) {
3612
+ for (var j = 0; j < lastChange.changes.length; j++) {
3613
+ var change = lastChange.changes[j];
3614
+ if (change instanceof InsertModeKey) {
3615
+ CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler);
3616
+ } else {
3617
+ var cur = cm.getCursor();
3618
+ cm.replaceRange(change, cur, cur);
3619
+ }
3620
+ }
3621
+ }
3622
+ }
3623
+
3302
3624
  return vimApi;
3303
3625
  };
3304
3626
  // Initialize Vim and make it available as an API.
3305
- var vim = Vim();
3306
- CodeMirror.Vim = vim;
3627
+ CodeMirror.Vim = Vim();
3307
3628
  }
3308
3629
  )();