codemirror-rails 3.02 → 3.12

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 (109) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +1 -1
  3. data/LICENSE +1 -1
  4. data/codemirror-rails.gemspec +1 -0
  5. data/doc/CodeMirror-LICENSE +5 -1
  6. data/lib/codemirror/rails/version.rb +2 -2
  7. data/vendor/assets/javascripts/codemirror/{utils → addons/dialog}/dialog.js +4 -0
  8. data/vendor/assets/javascripts/codemirror/addons/display/placeholder.js +54 -0
  9. data/vendor/assets/javascripts/codemirror/addons/edit/closebrackets.js +54 -0
  10. data/vendor/assets/javascripts/codemirror/{utils → addons/edit}/closetag.js +9 -8
  11. data/vendor/assets/javascripts/codemirror/{utils → addons/edit}/continuecomment.js +12 -4
  12. data/vendor/assets/javascripts/codemirror/addons/edit/continuelist.js +25 -0
  13. data/vendor/assets/javascripts/codemirror/{utils → addons/edit}/matchbrackets.js +30 -11
  14. data/vendor/assets/javascripts/codemirror/addons/fold/brace-fold.js +31 -0
  15. data/vendor/assets/javascripts/codemirror/addons/fold/foldcode.js +32 -0
  16. data/vendor/assets/javascripts/codemirror/addons/fold/indent-fold.js +11 -0
  17. data/vendor/assets/javascripts/codemirror/addons/fold/xml-fold.js +64 -0
  18. data/vendor/assets/javascripts/codemirror/addons/hint/html-hint.js +582 -0
  19. data/vendor/assets/javascripts/codemirror/{utils → addons/hint}/javascript-hint.js +15 -11
  20. data/vendor/assets/javascripts/codemirror/{utils → addons/hint}/pig-hint.js +19 -19
  21. data/vendor/assets/javascripts/codemirror/{utils → addons/hint}/python-hint.js +2 -2
  22. data/vendor/assets/javascripts/codemirror/addons/hint/show-hint.js +180 -0
  23. data/vendor/assets/javascripts/codemirror/{utils → addons/hint}/xml-hint.js +5 -18
  24. data/vendor/assets/javascripts/codemirror/addons/lint/javascript-lint.js +127 -0
  25. data/vendor/assets/javascripts/codemirror/addons/lint/json-lint.js +14 -0
  26. data/vendor/assets/javascripts/codemirror/addons/lint/lint.js +197 -0
  27. data/vendor/assets/javascripts/codemirror/{utils → addons/mode}/loadmode.js +0 -0
  28. data/vendor/assets/javascripts/codemirror/{utils → addons/mode}/multiplex.js +2 -2
  29. data/vendor/assets/javascripts/codemirror/{utils → addons/mode}/overlay.js +2 -2
  30. data/vendor/assets/javascripts/codemirror/{utils → addons/runmode}/colorize.js +0 -0
  31. data/vendor/assets/javascripts/codemirror/{utils → addons/runmode}/runmode-standalone.js +2 -3
  32. data/vendor/assets/javascripts/codemirror/{utils → addons/runmode}/runmode.js +0 -0
  33. data/vendor/assets/javascripts/codemirror/addons/runmode/runmode.node.js +89 -0
  34. data/vendor/assets/javascripts/codemirror/addons/search/match-highlighter.js +60 -0
  35. data/vendor/assets/javascripts/codemirror/{utils → addons/search}/search.js +5 -5
  36. data/vendor/assets/javascripts/codemirror/{utils → addons/search}/searchcursor.js +37 -30
  37. data/vendor/assets/javascripts/codemirror/addons/selection/active-line.js +39 -0
  38. data/vendor/assets/javascripts/codemirror/addons/selection/mark-selection.js +34 -0
  39. data/vendor/assets/javascripts/codemirror/keymaps/vim.js +721 -188
  40. data/vendor/assets/javascripts/codemirror/modes/asterisk.js +6 -6
  41. data/vendor/assets/javascripts/codemirror/modes/clike.js +14 -14
  42. data/vendor/assets/javascripts/codemirror/modes/clojure.js +23 -7
  43. data/vendor/assets/javascripts/codemirror/modes/coffeescript.js +2 -2
  44. data/vendor/assets/javascripts/codemirror/modes/css.js +337 -235
  45. data/vendor/assets/javascripts/codemirror/modes/ecl.js +12 -12
  46. data/vendor/assets/javascripts/codemirror/modes/erlang.js +1 -1
  47. data/vendor/assets/javascripts/codemirror/modes/gas.js +326 -0
  48. data/vendor/assets/javascripts/codemirror/modes/gfm.js +1 -0
  49. data/vendor/assets/javascripts/codemirror/modes/haskell.js +26 -26
  50. data/vendor/assets/javascripts/codemirror/modes/haxe.js +17 -17
  51. data/vendor/assets/javascripts/codemirror/modes/htmlembedded.js +6 -6
  52. data/vendor/assets/javascripts/codemirror/modes/htmlmixed.js +43 -23
  53. data/vendor/assets/javascripts/codemirror/modes/javascript.js +78 -33
  54. data/vendor/assets/javascripts/codemirror/modes/jinja2.js +2 -2
  55. data/vendor/assets/javascripts/codemirror/modes/less.js +38 -38
  56. data/vendor/assets/javascripts/codemirror/modes/livescript.js +267 -0
  57. data/vendor/assets/javascripts/codemirror/modes/lua.js +7 -7
  58. data/vendor/assets/javascripts/codemirror/modes/markdown.js +108 -57
  59. data/vendor/assets/javascripts/codemirror/modes/mirc.js +177 -0
  60. data/vendor/assets/javascripts/codemirror/modes/ntriples.js +22 -22
  61. data/vendor/assets/javascripts/codemirror/modes/ocaml.js +1 -1
  62. data/vendor/assets/javascripts/codemirror/modes/perl.js +791 -791
  63. data/vendor/assets/javascripts/codemirror/modes/php.js +1 -1
  64. data/vendor/assets/javascripts/codemirror/modes/pig.js +163 -163
  65. data/vendor/assets/javascripts/codemirror/modes/python.js +31 -31
  66. data/vendor/assets/javascripts/codemirror/modes/q.js +124 -0
  67. data/vendor/assets/javascripts/codemirror/modes/rst.js +486 -250
  68. data/vendor/assets/javascripts/codemirror/modes/sass.js +3 -3
  69. data/vendor/assets/javascripts/codemirror/modes/scss_test.js +80 -0
  70. data/vendor/assets/javascripts/codemirror/modes/shell.js +2 -2
  71. data/vendor/assets/javascripts/codemirror/modes/sieve.js +10 -10
  72. data/vendor/assets/javascripts/codemirror/modes/smalltalk.js +129 -129
  73. data/vendor/assets/javascripts/codemirror/modes/smarty.js +3 -3
  74. data/vendor/assets/javascripts/codemirror/modes/sparql.js +1 -1
  75. data/vendor/assets/javascripts/codemirror/modes/sql.js +23 -23
  76. data/vendor/assets/javascripts/codemirror/modes/stex.js +192 -121
  77. data/vendor/assets/javascripts/codemirror/modes/tcl.js +131 -0
  78. data/vendor/assets/javascripts/codemirror/modes/test.js +64 -0
  79. data/vendor/assets/javascripts/codemirror/modes/tiddlywiki.js +345 -345
  80. data/vendor/assets/javascripts/codemirror/modes/tiki.js +297 -298
  81. data/vendor/assets/javascripts/codemirror/modes/turtle.js +145 -0
  82. data/vendor/assets/javascripts/codemirror/modes/vb.js +31 -32
  83. data/vendor/assets/javascripts/codemirror/modes/vbscript.js +4 -4
  84. data/vendor/assets/javascripts/codemirror/modes/xml.js +10 -6
  85. data/vendor/assets/javascripts/codemirror/modes/xquery.js +55 -55
  86. data/vendor/assets/javascripts/codemirror/modes/yaml.js +90 -90
  87. data/vendor/assets/javascripts/codemirror/modes/z80.js +82 -110
  88. data/vendor/assets/javascripts/codemirror.js +1914 -1115
  89. data/vendor/assets/stylesheets/codemirror/{utils → addons/dialog}/dialog.css +0 -0
  90. data/vendor/assets/stylesheets/codemirror/addons/hint/show-hint.css +38 -0
  91. data/vendor/assets/stylesheets/codemirror/addons/lint/lint.css +96 -0
  92. data/vendor/assets/stylesheets/codemirror/modes/tiki.css +2 -2
  93. data/vendor/assets/stylesheets/codemirror/themes/ambiance-mobile.css +0 -1
  94. data/vendor/assets/stylesheets/codemirror/themes/ambiance.css +0 -1
  95. data/vendor/assets/stylesheets/codemirror/themes/eclipse.css +2 -2
  96. data/vendor/assets/stylesheets/codemirror/themes/erlang-dark.css +5 -5
  97. data/vendor/assets/stylesheets/codemirror/themes/midnight.css +52 -0
  98. data/vendor/assets/stylesheets/codemirror/themes/xq-light.css +43 -0
  99. data/vendor/assets/stylesheets/codemirror.css +16 -10
  100. metadata +60 -52
  101. data/vendor/assets/javascripts/codemirror/modes/mysql.js +0 -203
  102. data/vendor/assets/javascripts/codemirror/modes/plsql.js +0 -216
  103. data/vendor/assets/javascripts/codemirror/utils/collapserange.js +0 -68
  104. data/vendor/assets/javascripts/codemirror/utils/continuelist.js +0 -28
  105. data/vendor/assets/javascripts/codemirror/utils/foldcode.js +0 -183
  106. data/vendor/assets/javascripts/codemirror/utils/formatting.js +0 -114
  107. data/vendor/assets/javascripts/codemirror/utils/match-highlighter.js +0 -46
  108. data/vendor/assets/javascripts/codemirror/utils/simple-hint.js +0 -102
  109. data/vendor/assets/stylesheets/codemirror/utils/simple-hint.css +0 -16
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Motion:
5
5
  * h, j, k, l
6
+ * gj, gk
6
7
  * e, E, w, W, b, B, ge, gE
7
8
  * f<character>, F<character>, t<character>, T<character>
8
- * $, ^, 0
9
+ * $, ^, 0, -, +, _
9
10
  * gg, G
10
11
  * %
11
12
  * '<character>, `<character>
@@ -21,6 +22,7 @@
21
22
  *
22
23
  * Action:
23
24
  * a, i, s, A, I, S, o, O
25
+ * zz, z., z<CR>, zt, zb, z-
24
26
  * J
25
27
  * u, Ctrl-r
26
28
  * m<character>
@@ -77,6 +79,15 @@
77
79
  { keys: ['PageUp'], type: 'keyToKey', toKeys: ['Ctrl-b'] },
78
80
  { keys: ['PageDown'], type: 'keyToKey', toKeys: ['Ctrl-f'] },
79
81
  // Motions
82
+ { keys: ['H'], type: 'motion',
83
+ motion: 'moveToTopLine',
84
+ motionArgs: { linewise: true }},
85
+ { keys: ['M'], type: 'motion',
86
+ motion: 'moveToMiddleLine',
87
+ motionArgs: { linewise: true }},
88
+ { keys: ['L'], type: 'motion',
89
+ motion: 'moveToBottomLine',
90
+ motionArgs: { linewise: true }},
80
91
  { keys: ['h'], type: 'motion',
81
92
  motion: 'moveByCharacters',
82
93
  motionArgs: { forward: false }},
@@ -89,6 +100,12 @@
89
100
  { keys: ['k'], type: 'motion',
90
101
  motion: 'moveByLines',
91
102
  motionArgs: { forward: false, linewise: true }},
103
+ { keys: ['g','j'], type: 'motion',
104
+ motion: 'moveByDisplayLines',
105
+ motionArgs: { forward: true }},
106
+ { keys: ['g','k'], type: 'motion',
107
+ motion: 'moveByDisplayLines',
108
+ motionArgs: { forward: false }},
92
109
  { keys: ['w'], type: 'motion',
93
110
  motion: 'moveByWords',
94
111
  motionArgs: { forward: true, wordEnd: false }},
@@ -123,6 +140,12 @@
123
140
  motion: 'moveByPage', motionArgs: { forward: true }},
124
141
  { keys: ['Ctrl-b'], type: 'motion',
125
142
  motion: 'moveByPage', motionArgs: { forward: false }},
143
+ { keys: ['Ctrl-d'], type: 'motion',
144
+ motion: 'moveByScroll',
145
+ motionArgs: { forward: true, explicitRepeat: true }},
146
+ { keys: ['Ctrl-u'], type: 'motion',
147
+ motion: 'moveByScroll',
148
+ motionArgs: { forward: false, explicitRepeat: true }},
126
149
  { keys: ['g', 'g'], type: 'motion',
127
150
  motion: 'moveToLineOrEdgeOfDocument',
128
151
  motionArgs: { forward: false, explicitRepeat: true, linewise: true }},
@@ -132,6 +155,15 @@
132
155
  { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' },
133
156
  { keys: ['^'], type: 'motion',
134
157
  motion: 'moveToFirstNonWhiteSpaceCharacter' },
158
+ { keys: ['+'], type: 'motion',
159
+ motion: 'moveByLines',
160
+ motionArgs: { forward: true, toFirstChar:true }},
161
+ { keys: ['-'], type: 'motion',
162
+ motion: 'moveByLines',
163
+ motionArgs: { forward: false, toFirstChar:true }},
164
+ { keys: ['_'], type: 'motion',
165
+ motion: 'moveByLines',
166
+ motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
135
167
  { keys: ['$'], type: 'motion',
136
168
  motion: 'moveToEol',
137
169
  motionArgs: { inclusive: true }},
@@ -150,8 +182,22 @@
150
182
  { keys: ['T', 'character'], type: 'motion',
151
183
  motion: 'moveTillCharacter',
152
184
  motionArgs: { forward: false }},
185
+ { keys: [';'], type: 'motion', motion: 'repeatLastCharacterSearch',
186
+ motionArgs: { forward: true }},
187
+ { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch',
188
+ motionArgs: { forward: false }},
153
189
  { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark' },
154
190
  { keys: ['`', 'character'], type: 'motion', motion: 'goToMark' },
191
+ { keys: [']', '`',], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
192
+ { keys: ['[', '`',], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
193
+ { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
194
+ { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
195
+ { keys: [']', 'character'], type: 'motion',
196
+ motion: 'moveToSymbol',
197
+ motionArgs: { forward: true}},
198
+ { keys: ['[', 'character'], type: 'motion',
199
+ motion: 'moveToSymbol',
200
+ motionArgs: { forward: false}},
155
201
  { keys: ['|'], type: 'motion',
156
202
  motion: 'moveToColumn',
157
203
  motionArgs: { }},
@@ -165,8 +211,10 @@
165
211
  { keys: ['<'], type: 'operator', operator: 'indent',
166
212
  operatorArgs: { indentRight: false }},
167
213
  { keys: ['g', '~'], type: 'operator', operator: 'swapcase' },
168
- { keys: ['n'], type: 'motion', motion: 'findNext' },
169
- { keys: ['N'], type: 'motion', motion: 'findPrev' },
214
+ { keys: ['n'], type: 'motion', motion: 'findNext',
215
+ motionArgs: { forward: true }},
216
+ { keys: ['N'], type: 'motion', motion: 'findNext',
217
+ motionArgs: { forward: false }},
170
218
  // Operator-Motion dual commands
171
219
  { keys: ['x'], type: 'operatorMotion', operator: 'delete',
172
220
  motion: 'moveByCharacters', motionArgs: { forward: true },
@@ -211,7 +259,26 @@
211
259
  { keys: ['Ctrl-r'], type: 'action', action: 'redo' },
212
260
  { keys: ['m', 'character'], type: 'action', action: 'setMark' },
213
261
  { keys: ['\"', 'character'], type: 'action', action: 'setRegister' },
214
- { keys: [',', '/'], type: 'action', action: 'clearSearchHighlight' },
262
+ { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor',
263
+ actionArgs: { position: 'center' }},
264
+ { keys: ['z', '.'], type: 'action', action: 'scrollToCursor',
265
+ actionArgs: { position: 'center' },
266
+ motion: 'moveToFirstNonWhiteSpaceCharacter' },
267
+ { keys: ['z', 't'], type: 'action', action: 'scrollToCursor',
268
+ actionArgs: { position: 'top' }},
269
+ { keys: ['z', 'Enter'], type: 'action', action: 'scrollToCursor',
270
+ actionArgs: { position: 'top' },
271
+ motion: 'moveToFirstNonWhiteSpaceCharacter' },
272
+ { keys: ['z', '-'], type: 'action', action: 'scrollToCursor',
273
+ actionArgs: { position: 'bottom' }},
274
+ { keys: ['z', 'b'], type: 'action', action: 'scrollToCursor',
275
+ actionArgs: { position: 'bottom' },
276
+ motion: 'moveToFirstNonWhiteSpaceCharacter' },
277
+ { keys: ['.'], type: 'action', action: 'repeatLastEdit' },
278
+ { keys: ['Ctrl-a'], type: 'action', action: 'incrementNumberToken',
279
+ actionArgs: {increase: true, backtrack: false}},
280
+ { keys: ['Ctrl-x'], type: 'action', action: 'incrementNumberToken',
281
+ actionArgs: {increase: false, backtrack: false}},
215
282
  // Text object motions
216
283
  { keys: ['a', 'character'], type: 'motion',
217
284
  motion: 'textObjectManipulation' },
@@ -249,7 +316,7 @@
249
316
  var SPECIAL_SYMBOLS = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\'';
250
317
  var specialSymbols = SPECIAL_SYMBOLS.split('');
251
318
  var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace',
252
- 'Esc', 'Home', 'End', 'PageUp', 'PageDown'];
319
+ 'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter'];
253
320
  var validMarks = upperCaseAlphabet.concat(lowerCaseAlphabet).concat(
254
321
  numbers).concat(['<', '>']);
255
322
  var validRegisters = upperCaseAlphabet.concat(lowerCaseAlphabet).concat(
@@ -259,7 +326,7 @@
259
326
  return alphabetRegex.test(k);
260
327
  }
261
328
  function isLine(cm, line) {
262
- return line >= 0 && line < cm.lineCount();
329
+ return line >= cm.firstLine() && line <= cm.lastLine();
263
330
  }
264
331
  function isLowerCase(k) {
265
332
  return (/^[a-z]$/).test(k);
@@ -303,6 +370,8 @@
303
370
  searchQuery: null,
304
371
  // Whether we are searching backwards.
305
372
  searchIsReversed: false,
373
+ // Recording latest f, t, F or T motion command.
374
+ lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
306
375
  registerController: new RegisterController({})
307
376
  };
308
377
  }
@@ -319,6 +388,8 @@
319
388
  // cursor should go back to its horizontal position on the longer
320
389
  // line if it can. This is to keep track of the horizontal position.
321
390
  lastHPos: -1,
391
+ // Doing the same with screen-position for gj/gk
392
+ lastHSPos: -1,
322
393
  // The last motion command run. Cleared if a non-motion command gets
323
394
  // executed in between.
324
395
  lastMotion: null,
@@ -348,6 +419,12 @@
348
419
  // Add user defined key bindings.
349
420
  exCommandDispatcher.map(lhs, rhs);
350
421
  },
422
+ defineEx: function(name, prefix, func){
423
+ if (name.indexOf(prefix) === 0) {
424
+ exCommands[name]=func;
425
+ exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
426
+ }else throw new Error("(Vim.defineEx) \""+prefix+"\" is not a prefix of \""+name+"\", command not registered");
427
+ },
351
428
  // Initializes vim state variable on the CodeMirror object. Should only be
352
429
  // called lazily by handleKey or for testing.
353
430
  maybeInitState: function(cm) {
@@ -360,7 +437,7 @@
360
437
  var vim = getVimState(cm);
361
438
  if (key == 'Esc') {
362
439
  // Clear input state and get back to normal mode.
363
- vim.inputState.reset();
440
+ vim.inputState = new InputState();
364
441
  if (vim.visualMode) {
365
442
  exitVisualMode(cm, vim);
366
443
  }
@@ -400,9 +477,6 @@
400
477
 
401
478
  // Represents the current input state.
402
479
  function InputState() {
403
- this.reset();
404
- }
405
- InputState.prototype.reset = function() {
406
480
  this.prefixRepeat = [];
407
481
  this.motionRepeat = [];
408
482
 
@@ -412,7 +486,7 @@
412
486
  this.motionArgs = null;
413
487
  this.keyBuffer = []; // For matching multi-key commands.
414
488
  this.registerName = null; // Defaults to the unamed register.
415
- };
489
+ }
416
490
  InputState.prototype.pushRepeatDigit = function(n) {
417
491
  if (!this.operator) {
418
492
  this.prefixRepeat = this.prefixRepeat.concat(n);
@@ -567,6 +641,18 @@
567
641
  // Matches whole comand. Return the command.
568
642
  if (command.keys[keys.length - 1] == 'character') {
569
643
  inputState.selectedCharacter = keys[keys.length - 1];
644
+ if(inputState.selectedCharacter.length>1){
645
+ switch(inputState.selectedCharacter){
646
+ case "Enter":
647
+ inputState.selectedCharacter='\n';
648
+ break;
649
+ case "Space":
650
+ inputState.selectedCharacter=' ';
651
+ break;
652
+ default:
653
+ continue;
654
+ }
655
+ }
570
656
  }
571
657
  inputState.keyBuffer = [];
572
658
  return command;
@@ -578,6 +664,7 @@
578
664
  return null;
579
665
  },
580
666
  processCommand: function(cm, vim, command) {
667
+ vim.inputState.repeatOverride = command.repeatOverride;
581
668
  switch (command.type) {
582
669
  case 'motion':
583
670
  this.processMotion(cm, vim, command);
@@ -619,7 +706,7 @@
619
706
  return;
620
707
  } else {
621
708
  // 2 different operators in a row doesn't make sense.
622
- inputState.reset();
709
+ vim.inputState = new InputState();
623
710
  }
624
711
  }
625
712
  inputState.operator = command.operator;
@@ -664,7 +751,7 @@
664
751
  actionArgs.repeat = repeat || 1;
665
752
  actionArgs.repeatIsExplicit = repeatIsExplicit;
666
753
  actionArgs.registerName = inputState.registerName;
667
- inputState.reset();
754
+ vim.inputState = new InputState();
668
755
  vim.lastMotion = null,
669
756
  actions[command.action](cm, actionArgs, vim);
670
757
  },
@@ -676,19 +763,61 @@
676
763
  var forward = command.searchArgs.forward;
677
764
  getSearchState(cm).setReversed(!forward);
678
765
  var promptPrefix = (forward) ? '/' : '?';
766
+ var originalQuery = getSearchState(cm).getQuery();
767
+ var originalScrollPos = cm.getScrollInfo();
679
768
  function handleQuery(query, ignoreCase, smartCase) {
680
- updateSearchQuery(cm, query, ignoreCase, smartCase);
769
+ try {
770
+ updateSearchQuery(cm, query, ignoreCase, smartCase);
771
+ } catch (e) {
772
+ showConfirm(cm, 'Invalid regex: ' + regexPart);
773
+ return;
774
+ }
681
775
  commandDispatcher.processMotion(cm, vim, {
682
776
  type: 'motion',
683
- motion: 'findNext'
777
+ motion: 'findNext',
778
+ motionArgs: { forward: true }
684
779
  });
685
780
  }
686
781
  function onPromptClose(query) {
782
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
687
783
  handleQuery(query, true /** ignoreCase */, true /** smartCase */);
688
784
  }
785
+ function onPromptKeyUp(e, query) {
786
+ var parsedQuery;
787
+ try {
788
+ parsedQuery = updateSearchQuery(cm, query,
789
+ true /** ignoreCase */, true /** smartCase */)
790
+ } catch (e) {
791
+ // Swallow bad regexes for incremental search.
792
+ }
793
+ if (parsedQuery) {
794
+ cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);
795
+ } else {
796
+ clearSearchHighlight(cm);
797
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
798
+ }
799
+ }
800
+ function onPromptKeyDown(e, query, close) {
801
+ var keyName = CodeMirror.keyName(e);
802
+ if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
803
+ updateSearchQuery(cm, originalQuery);
804
+ clearSearchHighlight(cm);
805
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
806
+
807
+ CodeMirror.e_stop(e);
808
+ close();
809
+ cm.focus();
810
+ }
811
+ }
689
812
  switch (command.searchArgs.querySrc) {
690
813
  case 'prompt':
691
- showPrompt(cm, onPromptClose, promptPrefix, searchPromptDesc);
814
+ showPrompt(cm, {
815
+ onClose: onPromptClose,
816
+ prefix: promptPrefix,
817
+ desc: searchPromptDesc,
818
+ onKeyUp: onPromptKeyUp,
819
+ onKeyDown: onPromptKeyDown
820
+ });
692
821
  break;
693
822
  case 'wordUnderCursor':
694
823
  var word = expandWordUnderCursor(cm, false /** inclusive */,
@@ -720,14 +849,24 @@
720
849
  function onPromptClose(input) {
721
850
  exCommandDispatcher.processCommand(cm, input);
722
851
  }
852
+ function onPromptKeyDown(e, input, close) {
853
+ var keyName = CodeMirror.keyName(e);
854
+ if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
855
+ CodeMirror.e_stop(e);
856
+ close();
857
+ cm.focus();
858
+ }
859
+ }
723
860
  if (command.type == 'keyToEx') {
724
861
  // Handle user defined Ex to Ex mappings
725
862
  exCommandDispatcher.processCommand(cm, command.exArgs.input);
726
863
  } else {
727
864
  if (vim.visualMode) {
728
- showPrompt(cm, onPromptClose, ':', undefined, '\'<,\'>');
865
+ showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
866
+ onKeyDown: onPromptKeyDown});
729
867
  } else {
730
- showPrompt(cm, onPromptClose, ':');
868
+ showPrompt(cm, { onClose: onPromptClose, prefix: ':',
869
+ onKeyDown: onPromptKeyDown});
731
870
  }
732
871
  }
733
872
  },
@@ -748,10 +887,13 @@
748
887
  var curOriginal = copyCursor(curStart);
749
888
  var curEnd;
750
889
  var repeat;
751
- if (motionArgs.repeat !== undefined) {
752
- // If motionArgs specifies a repeat, that takes precedence over the
890
+ if (operator) {
891
+ this.recordLastEdit(cm, vim, inputState);
892
+ }
893
+ if (inputState.repeatOverride !== undefined) {
894
+ // If repeatOverride is specified, that takes precedence over the
753
895
  // input state's repeat. Used by Ex mode and can be user defined.
754
- repeat = inputState.motionArgs.repeat;
896
+ repeat = inputState.repeatOverride;
755
897
  } else {
756
898
  repeat = inputState.getRepeat();
757
899
  }
@@ -768,7 +910,7 @@
768
910
  inputState.selectedCharacter;
769
911
  }
770
912
  motionArgs.repeat = repeat;
771
- inputState.reset();
913
+ vim.inputState = new InputState();
772
914
  if (motion) {
773
915
  var motionResult = motions[motion](cm, motionArgs, vim);
774
916
  vim.lastMotion = motions[motion];
@@ -810,9 +952,6 @@
810
952
  selectionStart.ch = lineLength(cm, selectionStart.line);
811
953
  }
812
954
  }
813
- // Need to set the cursor to clear the selection. Otherwise,
814
- // CodeMirror can't figure out that we changed directions...
815
- cm.setCursor(selectionStart);
816
955
  cm.setSelection(selectionStart, selectionEnd);
817
956
  updateMark(cm, vim, '<',
818
957
  cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
@@ -868,6 +1007,9 @@
868
1007
  actions.enterInsertMode(cm);
869
1008
  }
870
1009
  }
1010
+ },
1011
+ recordLastEdit: function(cm, vim, inputState) {
1012
+ vim.lastEdit = inputState;
871
1013
  }
872
1014
  };
873
1015
 
@@ -877,6 +1019,19 @@
877
1019
  */
878
1020
  // All of the functions below return Cursor objects.
879
1021
  var motions = {
1022
+ moveToTopLine: function(cm, motionArgs) {
1023
+ var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
1024
+ return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) };
1025
+ },
1026
+ moveToMiddleLine: function(cm) {
1027
+ var range = getUserVisibleLines(cm);
1028
+ var line = Math.floor((range.top + range.bottom) * 0.5);
1029
+ return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) };
1030
+ },
1031
+ moveToBottomLine: function(cm, motionArgs) {
1032
+ var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
1033
+ return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) };
1034
+ },
880
1035
  expandToLine: function(cm, motionArgs) {
881
1036
  // Expands forward to end of line, and then to next line if repeat is
882
1037
  // >1. Does not handle backward motion!
@@ -884,10 +1039,16 @@
884
1039
  return { line: cur.line + motionArgs.repeat - 1, ch: Infinity };
885
1040
  },
886
1041
  findNext: function(cm, motionArgs, vim) {
887
- return findNext(cm, false /** prev */, motionArgs.repeat);
888
- },
889
- findPrev: function(cm, motionArgs, vim) {
890
- return findNext(cm, true /** prev */, motionArgs.repeat);
1042
+ var state = getSearchState(cm);
1043
+ var query = state.getQuery();
1044
+ if (!query) {
1045
+ return;
1046
+ }
1047
+ var prev = !motionArgs.forward;
1048
+ // If search is initiated with ? instead of /, negate direction.
1049
+ prev = (state.isReversed()) ? !prev : prev;
1050
+ highlightSearchMatches(cm, query);
1051
+ return findNext(cm, prev/** prev */, query, motionArgs.repeat);
891
1052
  },
892
1053
  goToMark: function(cm, motionArgs, vim) {
893
1054
  var mark = vim.marks[motionArgs.selectedCharacter];
@@ -896,6 +1057,44 @@
896
1057
  }
897
1058
  return null;
898
1059
  },
1060
+ jumpToMark: function(cm, motionArgs, vim) {
1061
+ var best = cm.getCursor();
1062
+ for (var i = 0; i < motionArgs.repeat; i++) {
1063
+ var cursor = best;
1064
+ for (var key in vim.marks) {
1065
+ if (!isLowerCase(key)) {
1066
+ continue;
1067
+ }
1068
+ var mark = vim.marks[key].find();
1069
+ var isWrongDirection = (motionArgs.forward) ?
1070
+ cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark)
1071
+
1072
+ if (isWrongDirection) {
1073
+ continue;
1074
+ }
1075
+ if (motionArgs.linewise && (mark.line == cursor.line)) {
1076
+ continue;
1077
+ }
1078
+
1079
+ var equal = cursorEqual(cursor, best);
1080
+ var between = (motionArgs.forward) ?
1081
+ cusrorIsBetween(cursor, mark, best) :
1082
+ cusrorIsBetween(best, mark, cursor);
1083
+
1084
+ if (equal || between) {
1085
+ best = mark;
1086
+ }
1087
+ }
1088
+ }
1089
+
1090
+ if (motionArgs.linewise) {
1091
+ // Vim places the cursor on the first non-whitespace character of
1092
+ // the line if there is one, else it places the cursor at the end
1093
+ // of the line, regardless of whether a mark was found.
1094
+ best.ch = findFirstNonWhiteSpaceCharacter(cm.getLine(best.line));
1095
+ }
1096
+ return best;
1097
+ },
899
1098
  moveByCharacters: function(cm, motionArgs) {
900
1099
  var cur = cm.getCursor();
901
1100
  var repeat = motionArgs.repeat;
@@ -903,7 +1102,8 @@
903
1102
  return { line: cur.line, ch: ch };
904
1103
  },
905
1104
  moveByLines: function(cm, motionArgs, vim) {
906
- var endCh = cm.getCursor().ch;
1105
+ var cur = cm.getCursor();
1106
+ var endCh = cur.ch;
907
1107
  // Depending what our last motion was, we may want to do different
908
1108
  // things. If our last motion was moving vertically, we want to
909
1109
  // preserve the HPos from our last horizontal move. If our last motion
@@ -911,6 +1111,8 @@
911
1111
  // the end of the line, etc.
912
1112
  switch (vim.lastMotion) {
913
1113
  case this.moveByLines:
1114
+ case this.moveByDisplayLines:
1115
+ case this.moveByScroll:
914
1116
  case this.moveToColumn:
915
1117
  case this.moveToEol:
916
1118
  endCh = vim.lastHPos;
@@ -918,14 +1120,46 @@
918
1120
  default:
919
1121
  vim.lastHPos = endCh;
920
1122
  }
921
- var cur = cm.getCursor();
922
- var repeat = motionArgs.repeat;
1123
+ var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
923
1124
  var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
924
- if (line < 0 || line > cm.lineCount() - 1) {
1125
+ if (line < cm.firstLine() || line > cm.lastLine() ) {
925
1126
  return null;
926
1127
  }
1128
+ if(motionArgs.toFirstChar){
1129
+ endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
1130
+ vim.lastHPos = endCh;
1131
+ }
1132
+ vim.lastHSPos = cm.charCoords({line:line, ch:endCh},"div").left;
927
1133
  return { line: line, ch: endCh };
928
1134
  },
1135
+ moveByDisplayLines: function(cm, motionArgs, vim) {
1136
+ var cur = cm.getCursor();
1137
+ switch (vim.lastMotion) {
1138
+ case this.moveByDisplayLines:
1139
+ case this.moveByScroll:
1140
+ case this.moveByLines:
1141
+ case this.moveToColumn:
1142
+ case this.moveToEol:
1143
+ break;
1144
+ default:
1145
+ vim.lastHSPos = cm.charCoords(cur,"div").left;
1146
+ }
1147
+ var repeat = motionArgs.repeat;
1148
+ var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),"line",vim.lastHSPos);
1149
+ if (res.hitSide) {
1150
+ if (motionArgs.forward) {
1151
+ var lastCharCoords = cm.charCoords(res, 'div');
1152
+ var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };
1153
+ var res = cm.coordsChar(goalCoords, 'div');
1154
+ } else {
1155
+ var resCoords = cm.charCoords({ line: cm.firstLine(), ch: 0}, 'div');
1156
+ resCoords.left = vim.lastHSPos;
1157
+ res = cm.coordsChar(resCoords, 'div');
1158
+ }
1159
+ }
1160
+ vim.lastHPos = res.ch;
1161
+ return res;
1162
+ },
929
1163
  moveByPage: function(cm, motionArgs) {
930
1164
  // CodeMirror only exposes functions that move the cursor page down, so
931
1165
  // doing this bad hack to move the cursor and move it back. evalInput
@@ -942,17 +1176,35 @@
942
1176
  var repeat = motionArgs.repeat;
943
1177
  var inc = motionArgs.forward ? 1 : -1;
944
1178
  for (var i = 0; i < repeat; i++) {
945
- if ((!motionArgs.forward && line === 0) ||
946
- (motionArgs.forward && line == cm.lineCount() - 1)) {
1179
+ if ((!motionArgs.forward && line === cm.firstLine() ) ||
1180
+ (motionArgs.forward && line == cm.lastLine())) {
947
1181
  break;
948
1182
  }
949
1183
  line += inc;
950
- while (line !== 0 && line != cm.lineCount - 1 && cm.getLine(line)) {
1184
+ while (line !== cm.firstLine() && line != cm.lastLine() && cm.getLine(line)) {
951
1185
  line += inc;
952
1186
  }
953
1187
  }
954
1188
  return { line: line, ch: 0 };
955
1189
  },
1190
+ moveByScroll: function(cm, motionArgs, vim) {
1191
+ var globalState = getVimGlobalState();
1192
+ var scrollbox = cm.getScrollInfo();
1193
+ var curEnd = null;
1194
+ var repeat = motionArgs.repeat;
1195
+ if (!repeat) {
1196
+ repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
1197
+ }
1198
+ var orig = cm.charCoords(cm.getCursor(), 'local');
1199
+ motionArgs.repeat = repeat;
1200
+ var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim);
1201
+ if (!curEnd) {
1202
+ return null;
1203
+ }
1204
+ var dest = cm.charCoords(curEnd, 'local');
1205
+ cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
1206
+ return curEnd;
1207
+ },
956
1208
  moveByWords: function(cm, motionArgs) {
957
1209
  return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward,
958
1210
  !!motionArgs.wordEnd, !!motionArgs.bigWord);
@@ -962,30 +1214,42 @@
962
1214
  var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
963
1215
  motionArgs.selectedCharacter);
964
1216
  var increment = motionArgs.forward ? -1 : 1;
1217
+ recordLastCharacterSearch(increment, motionArgs);
1218
+ if(!curEnd)return cm.getCursor();
965
1219
  curEnd.ch += increment;
966
1220
  return curEnd;
967
1221
  },
968
1222
  moveToCharacter: function(cm, motionArgs) {
969
1223
  var repeat = motionArgs.repeat;
1224
+ recordLastCharacterSearch(0, motionArgs);
970
1225
  return moveToCharacter(cm, repeat, motionArgs.forward,
971
- motionArgs.selectedCharacter);
1226
+ motionArgs.selectedCharacter) || cm.getCursor();
1227
+ },
1228
+ moveToSymbol: function(cm, motionArgs) {
1229
+ var repeat = motionArgs.repeat;
1230
+ return findSymbol(cm, repeat, motionArgs.forward,
1231
+ motionArgs.selectedCharacter) || cm.getCursor();
972
1232
  },
973
1233
  moveToColumn: function(cm, motionArgs, vim) {
974
1234
  var repeat = motionArgs.repeat;
975
1235
  // repeat is equivalent to which column we want to move to!
976
1236
  vim.lastHPos = repeat - 1;
1237
+ vim.lastHSPos = cm.charCoords(cm.getCursor(),"div").left;
977
1238
  return moveToColumn(cm, repeat);
978
1239
  },
979
1240
  moveToEol: function(cm, motionArgs, vim) {
980
1241
  var cur = cm.getCursor();
981
1242
  vim.lastHPos = Infinity;
982
- return { line: cur.line + motionArgs.repeat - 1, ch: Infinity };
1243
+ var retval={ line: cur.line + motionArgs.repeat - 1, ch: Infinity }
1244
+ var end=cm.clipPos(retval);
1245
+ end.ch--;
1246
+ vim.lastHSPos = cm.charCoords(end,"div").left;
1247
+ return retval;
983
1248
  },
984
1249
  moveToFirstNonWhiteSpaceCharacter: function(cm) {
985
1250
  // Go to the start of the line where the text begins, or the end for
986
1251
  // whitespace-only lines
987
1252
  var cursor = cm.getCursor();
988
- var line = cm.getLine(cursor.line);
989
1253
  return { line: cursor.line,
990
1254
  ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) };
991
1255
  },
@@ -1003,9 +1267,9 @@
1003
1267
  return { line: cursor.line, ch: 0 };
1004
1268
  },
1005
1269
  moveToLineOrEdgeOfDocument: function(cm, motionArgs) {
1006
- var lineNum = motionArgs.forward ? cm.lineCount() - 1 : 0;
1270
+ var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
1007
1271
  if (motionArgs.repeatIsExplicit) {
1008
- lineNum = motionArgs.repeat - 1;
1272
+ lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
1009
1273
  }
1010
1274
  return { line: lineNum,
1011
1275
  ch: findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)) };
@@ -1026,6 +1290,21 @@
1026
1290
  var start = tmp.start;
1027
1291
  var end = tmp.end;
1028
1292
  return [start, end];
1293
+ },
1294
+ repeatLastCharacterSearch: function(cm, motionArgs) {
1295
+ var lastSearch = getVimGlobalState().lastChararacterSearch;
1296
+ var repeat = motionArgs.repeat;
1297
+ var forward = motionArgs.forward === lastSearch.forward;
1298
+ var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
1299
+ cm.moveH(-increment, 'char');
1300
+ motionArgs.inclusive = forward ? true : false;
1301
+ var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
1302
+ if (!curEnd) {
1303
+ cm.moveH(increment, 'char')
1304
+ return cm.getCursor();
1305
+ }
1306
+ curEnd.ch += increment;
1307
+ return curEnd;
1029
1308
  }
1030
1309
  };
1031
1310
 
@@ -1104,7 +1383,25 @@
1104
1383
  };
1105
1384
 
1106
1385
  var actions = {
1107
- clearSearchHighlight: clearSearchHighlight,
1386
+ scrollToCursor: function(cm, actionArgs) {
1387
+ var lineNum = cm.getCursor().line;
1388
+ var heightProp = window.getComputedStyle(cm.getScrollerElement()).
1389
+ getPropertyValue('height');
1390
+ var height = parseInt(heightProp);
1391
+ var y = cm.charCoords({line: lineNum, ch: 0}, "local").top;
1392
+ var halfHeight = parseInt(height) / 2;
1393
+ switch (actionArgs.position) {
1394
+ case 'center': y = y - (height / 2) + 10;
1395
+ break;
1396
+ case 'bottom': y = y - height;
1397
+ break;
1398
+ case 'top': break;
1399
+ }
1400
+ cm.scrollTo(null, y);
1401
+ // The calculations are slightly off, use scrollIntoView to nudge the
1402
+ // view into the right place.
1403
+ cm.scrollIntoView();
1404
+ },
1108
1405
  enterInsertMode: function(cm, actionArgs) {
1109
1406
  var insertAt = (actionArgs) ? actionArgs.insertAt : null;
1110
1407
  if (insertAt == 'eol') {
@@ -1202,10 +1499,10 @@
1202
1499
  },
1203
1500
  newLineAndEnterInsertMode: function(cm, actionArgs) {
1204
1501
  var insertAt = cm.getCursor();
1205
- if (insertAt.line === 0 && !actionArgs.after) {
1502
+ if (insertAt.line === cm.firstLine() && !actionArgs.after) {
1206
1503
  // Special case for inserting newline before start of document.
1207
- cm.replaceRange('\n', { line: 0, ch: 0 });
1208
- cm.setCursor(0, 0);
1504
+ cm.replaceRange('\n', { line: cm.firstLine(), ch: 0 });
1505
+ cm.setCursor(cm.firstLine(), 0);
1209
1506
  } else {
1210
1507
  insertAt.line = (actionArgs.after) ? insertAt.line :
1211
1508
  insertAt.line - 1;
@@ -1272,21 +1569,82 @@
1272
1569
  var markName = actionArgs.selectedCharacter;
1273
1570
  updateMark(cm, vim, markName, cm.getCursor());
1274
1571
  },
1275
- replace: function(cm, actionArgs) {
1572
+ replace: function(cm, actionArgs, vim) {
1276
1573
  var replaceWith = actionArgs.selectedCharacter;
1277
1574
  var curStart = cm.getCursor();
1278
- var line = cm.getLine(curStart.line);
1279
- var replaceTo = curStart.ch + actionArgs.repeat;
1280
- if (replaceTo > line.length) {
1575
+ var replaceTo;
1576
+ var curEnd;
1577
+ if(vim.visualMode){
1578
+ curStart=cm.getCursor('start');
1579
+ curEnd=cm.getCursor('end');
1580
+ // workaround to catch the character under the cursor
1581
+ // existing workaround doesn't cover actions
1582
+ curEnd=cm.clipPos({line: curEnd.line, ch: curEnd.ch+1});
1583
+ }else{
1584
+ var line = cm.getLine(curStart.line);
1585
+ replaceTo = curStart.ch + actionArgs.repeat;
1586
+ if (replaceTo > line.length) {
1587
+ replaceTo=line.length;
1588
+ }
1589
+ curEnd = { line: curStart.line, ch: replaceTo };
1590
+ }
1591
+ if(replaceWith=='\n'){
1592
+ if(!vim.visualMode) cm.replaceRange('', curStart, curEnd);
1593
+ // special case, where vim help says to replace by just one line-break
1594
+ (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
1595
+ }else {
1596
+ var replaceWithStr=cm.getRange(curStart, curEnd);
1597
+ //replace all characters in range by selected, but keep linebreaks
1598
+ replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith);
1599
+ cm.replaceRange(replaceWithStr, curStart, curEnd);
1600
+ if(vim.visualMode){
1601
+ cm.setCursor(curStart);
1602
+ exitVisualMode(cm,vim);
1603
+ }else{
1604
+ cm.setCursor(offsetCursor(curEnd, 0, -1));
1605
+ }
1606
+ }
1607
+ },
1608
+ incrementNumberToken: function(cm, actionArgs, vim) {
1609
+ var cur = cm.getCursor();
1610
+ var lineStr = cm.getLine(cur.line);
1611
+ var re = /-?\d+/g;
1612
+ var match;
1613
+ var start;
1614
+ var end;
1615
+ var numberStr;
1616
+ var token;
1617
+ while ((match = re.exec(lineStr)) !== null) {
1618
+ token = match[0];
1619
+ start = match.index;
1620
+ end = start + token.length;
1621
+ if(cur.ch < end)break;
1622
+ }
1623
+ if(!actionArgs.backtrack && (end <= cur.ch))return;
1624
+ if (token) {
1625
+ var increment = actionArgs.increase ? 1 : -1;
1626
+ var number = parseInt(token) + (increment * actionArgs.repeat);
1627
+ var from = {ch:start, line:cur.line};
1628
+ var to = {ch:end, line:cur.line};
1629
+ numberStr = number.toString();
1630
+ cm.replaceRange(numberStr, from, to);
1631
+ } else {
1281
1632
  return;
1282
1633
  }
1283
- var curEnd = { line: curStart.line, ch: replaceTo };
1284
- var replaceWithStr = '';
1285
- for (var i = 0; i < curEnd.ch - curStart.ch; i++) {
1286
- replaceWithStr += replaceWith;
1634
+ cm.setCursor({line: cur.line, ch: start + numberStr.length - 1});
1635
+ },
1636
+ repeatLastEdit: function(cm, actionArgs, vim) {
1637
+ // TODO: Make this repeat insert mode changes.
1638
+ var lastEdit = vim.lastEdit;
1639
+ if (lastEdit) {
1640
+ if (actionArgs.repeat && actionArgs.repeatIsExplicit) {
1641
+ vim.lastEdit.repeatOverride = actionArgs.repeat;
1642
+ }
1643
+ var currentInputState = vim.inputState;
1644
+ vim.inputState = vim.lastEdit;
1645
+ commandDispatcher.evalInput(cm, vim);
1646
+ vim.inputState = currentInputState;
1287
1647
  }
1288
- cm.replaceRange(replaceWithStr, curStart, curEnd);
1289
- cm.setCursor(offsetCursor(curEnd, 0, -1));
1290
1648
  }
1291
1649
  };
1292
1650
 
@@ -1325,14 +1683,11 @@
1325
1683
  */
1326
1684
 
1327
1685
  /**
1328
- * Clips cursor to ensure that:
1329
- * 0 <= cur.ch < lineLength
1330
- * AND
1331
- * 0 <= cur.line < lineCount
1686
+ * Clips cursor to ensure that line is within the buffer's range
1332
1687
  * If includeLineBreak is true, then allow cur.ch == lineLength.
1333
1688
  */
1334
1689
  function clipCursorToContent(cm, cur, includeLineBreak) {
1335
- var line = Math.min(Math.max(0, cur.line), cm.lineCount() - 1);
1690
+ var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );
1336
1691
  var maxCh = lineLength(cm, line) - 1;
1337
1692
  maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
1338
1693
  var ch = Math.min(Math.max(0, cur.ch), maxCh);
@@ -1407,6 +1762,12 @@
1407
1762
  }
1408
1763
  return false;
1409
1764
  }
1765
+ function cusrorIsBetween(cur1, cur2, cur3) {
1766
+ // returns true if cur2 is between cur1 and cur3.
1767
+ var cur1before2 = cursorIsBefore(cur1, cur2);
1768
+ var cur2before3 = cursorIsBefore(cur2, cur3);
1769
+ return cur1before2 && cur2before3;
1770
+ }
1410
1771
  function lineLength(cm, lineNum) {
1411
1772
  return cm.getLine(lineNum).length;
1412
1773
  }
@@ -1515,6 +1876,134 @@
1515
1876
  end: { line: cur.line, ch: wordEnd }};
1516
1877
  }
1517
1878
 
1879
+ function recordLastCharacterSearch(increment, args) {
1880
+ var vimGlobalState = getVimGlobalState();
1881
+ vimGlobalState.lastChararacterSearch.increment = increment;
1882
+ vimGlobalState.lastChararacterSearch.forward = args.forward;
1883
+ vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter;
1884
+ }
1885
+
1886
+ var symbolToMode = {
1887
+ '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',
1888
+ '[': 'section', ']': 'section',
1889
+ '*': 'comment', '/': 'comment',
1890
+ 'm': 'method', 'M': 'method',
1891
+ '#': 'preprocess'
1892
+ };
1893
+ var findSymbolModes = {
1894
+ bracket: {
1895
+ isComplete: function(state) {
1896
+ if (state.nextCh === state.symb) {
1897
+ state.depth++;
1898
+ if(state.depth >= 1)return true;
1899
+ } else if (state.nextCh === state.reverseSymb) {
1900
+ state.depth--;
1901
+ }
1902
+ return false;
1903
+ }
1904
+ },
1905
+ section: {
1906
+ init: function(state) {
1907
+ state.curMoveThrough = true;
1908
+ state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';
1909
+ },
1910
+ isComplete: function(state) {
1911
+ return state.index === 0 && state.nextCh === state.symb;
1912
+ }
1913
+ },
1914
+ comment: {
1915
+ isComplete: function(state) {
1916
+ var found = state.lastCh === '*' && state.nextCh === '/';
1917
+ state.lastCh = state.nextCh;
1918
+ return found;
1919
+ }
1920
+ },
1921
+ // TODO: The original Vim implementation only operates on level 1 and 2.
1922
+ // The current implementation doesn't check for code block level and
1923
+ // therefore it operates on any levels.
1924
+ method: {
1925
+ init: function(state) {
1926
+ state.symb = (state.symb === 'm' ? '{' : '}');
1927
+ state.reverseSymb = state.symb === '{' ? '}' : '{';
1928
+ },
1929
+ isComplete: function(state) {
1930
+ if(state.nextCh === state.symb)return true;
1931
+ return false;
1932
+ }
1933
+ },
1934
+ preprocess: {
1935
+ init: function(state) {
1936
+ state.index = 0;
1937
+ },
1938
+ isComplete: function(state) {
1939
+ if (state.nextCh === '#') {
1940
+ var token = state.lineText.match(/#(\w+)/)[1];
1941
+ if (token === 'endif') {
1942
+ if (state.forward && state.depth === 0) {
1943
+ return true;
1944
+ }
1945
+ state.depth++;
1946
+ } else if (token === 'if') {
1947
+ if (!state.forward && state.depth === 0) {
1948
+ return true;
1949
+ }
1950
+ state.depth--;
1951
+ }
1952
+ if(token === 'else' && state.depth === 0)return true;
1953
+ }
1954
+ return false;
1955
+ }
1956
+ }
1957
+ };
1958
+ function findSymbol(cm, repeat, forward, symb) {
1959
+ var cur = cm.getCursor();
1960
+ var increment = forward ? 1 : -1;
1961
+ var endLine = forward ? cm.lineCount() : -1;
1962
+ var curCh = cur.ch;
1963
+ var line = cur.line;
1964
+ var lineText = cm.getLine(line);
1965
+ var state = {
1966
+ lineText: lineText,
1967
+ nextCh: lineText.charAt(curCh),
1968
+ lastCh: null,
1969
+ index: curCh,
1970
+ symb: symb,
1971
+ reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
1972
+ forward: forward,
1973
+ depth: 0,
1974
+ curMoveThrough: false
1975
+ };
1976
+ var mode = symbolToMode[symb];
1977
+ if(!mode)return cur;
1978
+ var init = findSymbolModes[mode].init;
1979
+ var isComplete = findSymbolModes[mode].isComplete;
1980
+ if(init)init(state);
1981
+ while (line !== endLine && repeat) {
1982
+ state.index += increment;
1983
+ state.nextCh = state.lineText.charAt(state.index);
1984
+ if (!state.nextCh) {
1985
+ line += increment;
1986
+ state.lineText = cm.getLine(line) || '';
1987
+ if (increment > 0) {
1988
+ state.index = 0;
1989
+ } else {
1990
+ var lineLen = state.lineText.length;
1991
+ state.index = (lineLen > 0) ? (lineLen-1) : 0;
1992
+ }
1993
+ state.nextCh = state.lineText.charAt(state.index);
1994
+ }
1995
+ if (isComplete(state)) {
1996
+ cur.line = line;
1997
+ cur.ch = state.index;
1998
+ repeat--;
1999
+ }
2000
+ }
2001
+ if (state.nextCh || state.curMoveThrough) {
2002
+ return { line: line, ch: state.index };
2003
+ }
2004
+ return cur;
2005
+ }
2006
+
1518
2007
  /*
1519
2008
  * Returns the boundaries of the next word. If the cursor in the middle of
1520
2009
  * the word, then returns the boundaries of the current word, starting at
@@ -1654,7 +2143,7 @@
1654
2143
  var line = cm.getLine(cur.line);
1655
2144
  idx = charIdxInLine(start, line, character, forward, true);
1656
2145
  if (idx == -1) {
1657
- return cur;
2146
+ return null;
1658
2147
  }
1659
2148
  start = idx;
1660
2149
  }
@@ -1703,9 +2192,6 @@
1703
2192
  var line = cur.line;
1704
2193
  symb = symb ? symb : cm.getLine(line).charAt(cur.ch);
1705
2194
 
1706
- // Are we at the opening or closing char
1707
- var forwards = inArray(symb, ['(', '[', '{']);
1708
-
1709
2195
  var reverseSymb = ({
1710
2196
  '(': ')', ')': '(',
1711
2197
  '[': ']', ']': '[',
@@ -1719,18 +2205,24 @@
1719
2205
  // set our increment to move forward (+1) or backwards (-1)
1720
2206
  // depending on which bracket we're matching
1721
2207
  var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1;
2208
+ var endLine = increment === 1 ? cm.lineCount() : -1;
1722
2209
  var depth = 1, nextCh = symb, index = cur.ch, lineText = cm.getLine(line);
1723
2210
  // Simple search for closing paren--just count openings and closings till
1724
2211
  // we find our match
1725
2212
  // TODO: use info from CodeMirror to ignore closing brackets in comments
1726
2213
  // and quotes, etc.
1727
- while (nextCh && depth > 0) {
2214
+ while (line !== endLine && depth > 0) {
1728
2215
  index += increment;
1729
2216
  nextCh = lineText.charAt(index);
1730
2217
  if (!nextCh) {
1731
2218
  line += increment;
1732
- index = 0;
1733
2219
  lineText = cm.getLine(line) || '';
2220
+ if (increment > 0) {
2221
+ index = 0;
2222
+ } else {
2223
+ var lineLen = lineText.length;
2224
+ index = (lineLen > 0) ? (lineLen-1) : 0;
2225
+ }
1734
2226
  nextCh = lineText.charAt(index);
1735
2227
  }
1736
2228
  if (nextCh === symb) {
@@ -1829,10 +2321,7 @@
1829
2321
  }
1830
2322
 
1831
2323
  // Search functions
1832
- function SearchState() {
1833
- // Highlighted text that match the query.
1834
- this.marked = null;
1835
- }
2324
+ function SearchState() {}
1836
2325
  SearchState.prototype = {
1837
2326
  getQuery: function() {
1838
2327
  return getVimGlobalState().query;
@@ -1840,12 +2329,6 @@
1840
2329
  setQuery: function(query) {
1841
2330
  getVimGlobalState().query = query;
1842
2331
  },
1843
- getMarked: function() {
1844
- return this.marked;
1845
- },
1846
- setMarked: function(marked) {
1847
- this.marked = marked;
1848
- },
1849
2332
  getOverlay: function() {
1850
2333
  return this.searchOverlay;
1851
2334
  },
@@ -1863,9 +2346,10 @@
1863
2346
  var vim = getVimState(cm);
1864
2347
  return vim.searchState_ || (vim.searchState_ = new SearchState());
1865
2348
  }
1866
- function dialog(cm, text, shortText, callback, initialValue) {
2349
+ function dialog(cm, template, shortText, onClose, options) {
1867
2350
  if (cm.openDialog) {
1868
- cm.openDialog(text, callback, { bottom: true, value: initialValue });
2351
+ cm.openDialog(template, onClose, { bottom: true, value: options.value,
2352
+ onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp });
1869
2353
  }
1870
2354
  else {
1871
2355
  callback(prompt(shortText, ""));
@@ -1894,6 +2378,8 @@
1894
2378
  * through to the Regex object.
1895
2379
  */
1896
2380
  function parseQuery(cm, query, ignoreCase, smartCase) {
2381
+ // Check if the query is already a regex.
2382
+ if (query instanceof RegExp) { return query; }
1897
2383
  // First try to extract regex + flags from the input. If no flags found,
1898
2384
  // extract just the regex. IE does not accept flags directly defined in
1899
2385
  // the regex string in the form /regex/flags
@@ -1915,13 +2401,9 @@
1915
2401
  if (smartCase) {
1916
2402
  ignoreCase = (/^[^A-Z]*$/).test(regexPart);
1917
2403
  }
1918
- try {
1919
- var regexp = new RegExp(regexPart,
1920
- (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
1921
- return regexp;
1922
- } catch (e) {
1923
- showConfirm(cm, 'Invalid regex: ' + regexPart);
1924
- }
2404
+ var regexp = new RegExp(regexPart,
2405
+ (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
2406
+ return regexp;
1925
2407
  }
1926
2408
  function showConfirm(cm, text) {
1927
2409
  if (cm.openConfirm) {
@@ -1947,10 +2429,10 @@
1947
2429
  return raw;
1948
2430
  }
1949
2431
  var searchPromptDesc = '(Javascript regexp)';
1950
- function showPrompt(cm, onPromptClose, prefix, desc, initialValue) {
1951
- var shortText = (prefix || '') + ' ' + (desc || '');
1952
- dialog(cm, makePrompt(prefix, desc), shortText, onPromptClose,
1953
- initialValue);
2432
+ function showPrompt(cm, options) {
2433
+ var shortText = (options.prefix || '') + ' ' + (options.desc || '');
2434
+ var prompt = makePrompt(options.prefix, options.desc);
2435
+ dialog(cm, prompt, shortText, options.onClose, options);
1954
2436
  }
1955
2437
  function regexEqual(r1, r2) {
1956
2438
  if (r1 instanceof RegExp && r2 instanceof RegExp) {
@@ -1965,29 +2447,40 @@
1965
2447
  }
1966
2448
  return(false);
1967
2449
  }
2450
+ // Returns true if the query is valid.
1968
2451
  function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
1969
- cm.operation(function() {
1970
- var state = getSearchState(cm);
1971
- if (!rawQuery) {
1972
- return;
1973
- }
1974
- var query = parseQuery(cm, rawQuery, !!ignoreCase, !!smartCase);
1975
- if (!query) {
1976
- return;
1977
- }
1978
- if (regexEqual(query, state.getQuery())) {
1979
- return;
1980
- }
1981
- clearSearchHighlight(cm);
1982
- highlightSearchMatches(cm, query);
1983
- state.setQuery(query);
1984
- });
2452
+ if (!rawQuery) {
2453
+ return;
2454
+ }
2455
+ var state = getSearchState(cm);
2456
+ var query = parseQuery(cm, rawQuery, !!ignoreCase, !!smartCase);
2457
+ if (!query) {
2458
+ return;
2459
+ }
2460
+ highlightSearchMatches(cm, query);
2461
+ if (regexEqual(query, state.getQuery())) {
2462
+ return query;
2463
+ }
2464
+ state.setQuery(query);
2465
+ return query;
1985
2466
  }
1986
2467
  function searchOverlay(query) {
2468
+ if (query.source.charAt(0) == '^') {
2469
+ var matchSol = true;
2470
+ }
1987
2471
  return {
1988
2472
  token: function(stream) {
2473
+ if (matchSol && !stream.sol()) {
2474
+ stream.skipToEnd();
2475
+ return;
2476
+ }
1989
2477
  var match = stream.match(query, false);
1990
2478
  if (match) {
2479
+ if (match[0].length == 0) {
2480
+ // Matched empty string, skip to next.
2481
+ stream.next();
2482
+ return;
2483
+ }
1991
2484
  if (!stream.sol()) {
1992
2485
  // Backtrack 1 to match \b
1993
2486
  stream.backUp(1);
@@ -2008,43 +2501,20 @@
2008
2501
  };
2009
2502
  }
2010
2503
  function highlightSearchMatches(cm, query) {
2011
- if (cm.addOverlay) {
2012
- var overlay = getSearchState(cm).getOverlay();
2013
- if (!overlay || query != overlay.query) {
2014
- if (overlay) {
2015
- cm.removeOverlay(overlay);
2016
- }
2017
- overlay = searchOverlay(query);
2018
- cm.addOverlay(overlay);
2019
- getSearchState(cm).setOverlay(overlay);
2020
- }
2021
- } else {
2022
- // TODO: Highlight only text inside the viewport. Highlighting everything
2023
- // is inefficient and expensive.
2024
- if (cm.lineCount() < 2000) { // This is too expensive on big documents.
2025
- var marked = [];
2026
- for (var cursor = cm.getSearchCursor(query);
2027
- cursor.findNext();) {
2028
- marked.push(cm.markText(cursor.from(), cursor.to(),
2029
- { className: 'cm-searching' }));
2030
- }
2031
- getSearchState(cm).setMarked(marked);
2504
+ var overlay = getSearchState(cm).getOverlay();
2505
+ if (!overlay || query != overlay.query) {
2506
+ if (overlay) {
2507
+ cm.removeOverlay(overlay);
2032
2508
  }
2509
+ overlay = searchOverlay(query);
2510
+ cm.addOverlay(overlay);
2511
+ getSearchState(cm).setOverlay(overlay);
2033
2512
  }
2034
2513
  }
2035
- function findNext(cm, prev, repeat) {
2514
+ function findNext(cm, prev, query, repeat) {
2515
+ if (repeat === undefined) { repeat = 1; }
2036
2516
  return cm.operation(function() {
2037
- var state = getSearchState(cm);
2038
- var query = state.getQuery();
2039
- if (!query) {
2040
- return;
2041
- }
2042
- if (!state.getMarked()) {
2043
- highlightSearchMatches(cm, query);
2044
- }
2045
2517
  var pos = cm.getCursor();
2046
- // If search is initiated with ? instead of /, negate direction.
2047
- prev = (state.isReversed()) ? !prev : prev;
2048
2518
  if (!prev) {
2049
2519
  pos.ch += 1;
2050
2520
  }
@@ -2054,7 +2524,7 @@
2054
2524
  // SearchCursor may have returned null because it hit EOF, wrap
2055
2525
  // around and try again.
2056
2526
  cursor = cm.getSearchCursor(query,
2057
- (prev) ? { line: cm.lineCount() - 1} : {line: 0, ch: 0} );
2527
+ (prev) ? { line: cm.lastLine() } : {line: cm.firstLine(), ch: 0} );
2058
2528
  if (!cursor.find(prev)) {
2059
2529
  return;
2060
2530
  }
@@ -2063,25 +2533,8 @@
2063
2533
  return cursor.from();
2064
2534
  });}
2065
2535
  function clearSearchHighlight(cm) {
2066
- if (cm.addOverlay) {
2067
- cm.removeOverlay(getSearchState(cm).getOverlay());
2068
- getSearchState(cm).setOverlay(null);
2069
- } else {
2070
- cm.operation(function() {
2071
- var state = getSearchState(cm);
2072
- if (!state.getQuery()) {
2073
- return;
2074
- }
2075
- var marked = state.getMarked();
2076
- if (!marked) {
2077
- return;
2078
- }
2079
- for (var i = 0; i < marked.length; ++i) {
2080
- marked[i].clear();
2081
- }
2082
- state.setMarked(null);
2083
- });
2084
- }
2536
+ cm.removeOverlay(getSearchState(cm).getOverlay());
2537
+ getSearchState(cm).setOverlay(null);
2085
2538
  }
2086
2539
  /**
2087
2540
  * Check if pos is in the specified range, INCLUSIVE.
@@ -2109,6 +2562,15 @@
2109
2562
  }
2110
2563
  }
2111
2564
  }
2565
+ function getUserVisibleLines(cm) {
2566
+ var scrollInfo = cm.getScrollInfo();
2567
+ var occludeTorleranceTop = 6;
2568
+ var occludeTorleranceBottom = 10;
2569
+ var from = cm.coordsChar({left:0, top: occludeTorleranceTop}, 'local');
2570
+ var bottomY = scrollInfo.clientHeight - occludeTorleranceBottom;
2571
+ var to = cm.coordsChar({left:0, top: bottomY}, 'local');
2572
+ return {top: from.line, bottom: to.line};
2573
+ }
2112
2574
 
2113
2575
  // Ex command handling
2114
2576
  // Care must be taken when adding to the default Ex command map. For any
@@ -2119,7 +2581,9 @@
2119
2581
  { name: 'write', shortName: 'w', type: 'builtIn' },
2120
2582
  { name: 'undo', shortName: 'u', type: 'builtIn' },
2121
2583
  { name: 'redo', shortName: 'red', type: 'builtIn' },
2122
- { name: 'substitute', shortName: 's', type: 'builtIn'}
2584
+ { name: 'substitute', shortName: 's', type: 'builtIn'},
2585
+ { name: 'nohlsearch', shortName: 'noh', type: 'builtIn'},
2586
+ { name: 'delmarks', shortName: 'delm', type: 'builtin'}
2123
2587
  ];
2124
2588
  Vim.ExCommandDispatcher = function() {
2125
2589
  this.buildCommandMap_();
@@ -2169,8 +2633,8 @@
2169
2633
  inputStream.eatWhile(':');
2170
2634
  // Parse range.
2171
2635
  if (inputStream.eat('%')) {
2172
- result.line = 0;
2173
- result.lineEnd = cm.lineCount() - 1;
2636
+ result.line = cm.firstLine();
2637
+ result.lineEnd = cm.lastLine();
2174
2638
  } else {
2175
2639
  result.line = this.parseLineSpec_(cm, inputStream);
2176
2640
  if (result.line !== undefined && inputStream.eat(',')) {
@@ -2197,7 +2661,7 @@
2197
2661
  case '.':
2198
2662
  return cm.getCursor().line;
2199
2663
  case '$':
2200
- return cm.lineCount() - 1;
2664
+ return cm.lastLine();
2201
2665
  case '\'':
2202
2666
  var mark = getVimState(cm).marks[inputStream.next()];
2203
2667
  if (mark && mark.find()) {
@@ -2300,25 +2764,28 @@
2300
2764
  var vimKeyNotationStart = ++idx;
2301
2765
  while (str.charAt(idx++) != '>') {}
2302
2766
  var vimKeyNotation = str.substring(vimKeyNotationStart, idx - 1);
2767
+ var mod='';
2303
2768
  var match = (/^C-(.+)$/).exec(vimKeyNotation);
2304
2769
  if (match) {
2305
- var key;
2306
- switch (match[1]) {
2307
- case 'BS':
2308
- key = 'Backspace';
2309
- break;
2310
- case 'CR':
2311
- key = 'Enter';
2312
- break;
2313
- case 'Del':
2314
- key = 'Delete';
2315
- break;
2316
- default:
2317
- key = match[1];
2318
- break;
2319
- }
2320
- keys.push('Ctrl-' + key);
2770
+ mod='Ctrl-';
2771
+ vimKeyNotation=match[1];
2321
2772
  }
2773
+ var key;
2774
+ switch (vimKeyNotation) {
2775
+ case 'BS':
2776
+ key = 'Backspace';
2777
+ break;
2778
+ case 'CR':
2779
+ key = 'Enter';
2780
+ break;
2781
+ case 'Del':
2782
+ key = 'Delete';
2783
+ break;
2784
+ default:
2785
+ key = vimKeyNotation;
2786
+ break;
2787
+ }
2788
+ keys.push(mod + key);
2322
2789
  }
2323
2790
  return keys;
2324
2791
  }
@@ -2335,10 +2802,12 @@
2335
2802
  exCommandDispatcher.map(mapArgs[0], mapArgs[1], cm);
2336
2803
  },
2337
2804
  move: function(cm, params) {
2338
- commandDispatcher.processMotion(cm, getVimState(cm), {
2805
+ commandDispatcher.processCommand(cm, getVimState(cm), {
2806
+ type: 'motion',
2339
2807
  motion: 'moveToLineOrEdgeOfDocument',
2340
2808
  motionArgs: { forward: false, explicitRepeat: true,
2341
- linewise: true, repeat: params.line }});
2809
+ linewise: true },
2810
+ repeatOverride: params.line+1});
2342
2811
  },
2343
2812
  substitute: function(cm, params) {
2344
2813
  var argString = params.argString;
@@ -2368,12 +2837,17 @@
2368
2837
  if (regexPart) {
2369
2838
  // If regex part is empty, then use the previous query. Otherwise use
2370
2839
  // the regex part as the new query.
2371
- updateSearchQuery(cm, regexPart, true /** ignoreCase */,
2372
- true /** smartCase */);
2840
+ try {
2841
+ updateSearchQuery(cm, regexPart, true /** ignoreCase */,
2842
+ true /** smartCase */);
2843
+ } catch (e) {
2844
+ showConfirm(cm, 'Invalid regex: ' + regexPart);
2845
+ return;
2846
+ }
2373
2847
  }
2374
2848
  var state = getSearchState(cm);
2375
2849
  var query = state.getQuery();
2376
- var lineStart = params.line || 0;
2850
+ var lineStart = params.line || cm.firstLine();
2377
2851
  var lineEnd = params.lineEnd || lineStart;
2378
2852
  if (count) {
2379
2853
  lineStart = lineEnd;
@@ -2393,12 +2867,7 @@
2393
2867
  exitVisualMode(cm, vim);
2394
2868
  }
2395
2869
  }
2396
- if (cm.compoundChange) {
2397
- // Only exists in v2
2398
- cm.compoundChange(doReplace);
2399
- } else {
2400
- cm.operation(doReplace);
2401
- }
2870
+ cm.operation(doReplace);
2402
2871
  },
2403
2872
  redo: CodeMirror.commands.redo,
2404
2873
  undo: CodeMirror.commands.undo,
@@ -2410,6 +2879,70 @@
2410
2879
  // Saves to text area if no save command is defined.
2411
2880
  cm.save();
2412
2881
  }
2882
+ },
2883
+ nohlsearch: function(cm) {
2884
+ clearSearchHighlight(cm);
2885
+ },
2886
+ delmarks: function(cm, params) {
2887
+ if (!params.argString || !params.argString.trim()) {
2888
+ showConfirm(cm, 'Argument required');
2889
+ return;
2890
+ }
2891
+
2892
+ var state = getVimState(cm);
2893
+ var stream = new CodeMirror.StringStream(params.argString.trim());
2894
+ while (!stream.eol()) {
2895
+ stream.eatSpace();
2896
+
2897
+ // Record the streams position at the beginning of the loop for use
2898
+ // in error messages.
2899
+ var count = stream.pos;
2900
+
2901
+ if (!stream.match(/[a-zA-Z]/, false)) {
2902
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
2903
+ return;
2904
+ }
2905
+
2906
+ var sym = stream.next();
2907
+ // Check if this symbol is part of a range
2908
+ if (stream.match('-', true)) {
2909
+ // This symbol is part of a range.
2910
+
2911
+ // The range must terminate at an alphabetic character.
2912
+ if (!stream.match(/[a-zA-Z]/, false)) {
2913
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
2914
+ return;
2915
+ }
2916
+
2917
+ var startMark = sym;
2918
+ var finishMark = stream.next();
2919
+ // The range must terminate at an alphabetic character which
2920
+ // shares the same case as the start of the range.
2921
+ if (isLowerCase(startMark) && isLowerCase(finishMark) ||
2922
+ isUpperCase(startMark) && isUpperCase(finishMark)) {
2923
+ var start = startMark.charCodeAt(0);
2924
+ var finish = finishMark.charCodeAt(0);
2925
+ if (start >= finish) {
2926
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
2927
+ return;
2928
+ }
2929
+
2930
+ // Because marks are always ASCII values, and we have
2931
+ // determined that they are the same case, we can use
2932
+ // their char codes to iterate through the defined range.
2933
+ for (var j = 0; j <= finish - start; j++) {
2934
+ var mark = String.fromCharCode(start + j);
2935
+ delete state.marks[mark];
2936
+ }
2937
+ } else {
2938
+ showConfirm(cm, 'Invalid argument: ' + startMark + "-");
2939
+ return;
2940
+ }
2941
+ } else {
2942
+ // This symbol is a valid mark, and is not part of a range.
2943
+ delete state.marks[sym];
2944
+ }
2945
+ }
2413
2946
  }
2414
2947
  };
2415
2948