codemirror-rails 3.02 → 3.12

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