codemirror-rails 3.12 → 3.13

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/codemirror-rails.gemspec +1 -1
  3. data/lib/codemirror/rails/version.rb +2 -2
  4. data/vendor/assets/javascripts/codemirror.js +168 -116
  5. data/vendor/assets/javascripts/codemirror/addons/comment/comment.js +144 -0
  6. data/vendor/assets/javascripts/codemirror/addons/display/placeholder.js +4 -4
  7. data/vendor/assets/javascripts/codemirror/addons/edit/closebrackets.js +2 -2
  8. data/vendor/assets/javascripts/codemirror/addons/fold/brace-fold.js +9 -3
  9. data/vendor/assets/javascripts/codemirror/addons/lint/lint.js +13 -13
  10. data/vendor/assets/javascripts/codemirror/addons/runmode/runmode.js +5 -1
  11. data/vendor/assets/javascripts/codemirror/addons/runmode/runmode.node.js +13 -1
  12. data/vendor/assets/javascripts/codemirror/addons/search/match-highlighter.js +4 -4
  13. data/vendor/assets/javascripts/codemirror/addons/search/search.js +1 -1
  14. data/vendor/assets/javascripts/codemirror/addons/search/searchcursor.js +15 -5
  15. data/vendor/assets/javascripts/codemirror/addons/selection/active-line.js +6 -6
  16. data/vendor/assets/javascripts/codemirror/addons/selection/mark-selection.js +89 -15
  17. data/vendor/assets/javascripts/codemirror/keymaps/vim.js +451 -187
  18. data/vendor/assets/javascripts/codemirror/modes/clike.js +6 -3
  19. data/vendor/assets/javascripts/codemirror/modes/clojure.js +3 -1
  20. data/vendor/assets/javascripts/codemirror/modes/cobol.js +240 -0
  21. data/vendor/assets/javascripts/codemirror/modes/coffeescript.js +2 -1
  22. data/vendor/assets/javascripts/codemirror/modes/commonlisp.js +5 -1
  23. data/vendor/assets/javascripts/codemirror/modes/css.js +63 -24
  24. data/vendor/assets/javascripts/codemirror/modes/erlang.js +3 -2
  25. data/vendor/assets/javascripts/codemirror/modes/gas.js +5 -1
  26. data/vendor/assets/javascripts/codemirror/modes/go.js +4 -1
  27. data/vendor/assets/javascripts/codemirror/modes/haml.js +153 -0
  28. data/vendor/assets/javascripts/codemirror/modes/haskell.js +5 -1
  29. data/vendor/assets/javascripts/codemirror/modes/javascript.js +5 -1
  30. data/vendor/assets/javascripts/codemirror/modes/less.js +0 -8
  31. data/vendor/assets/javascripts/codemirror/modes/lua.js +5 -1
  32. data/vendor/assets/javascripts/codemirror/modes/markdown.js +2 -2
  33. data/vendor/assets/javascripts/codemirror/modes/ocaml.js +4 -1
  34. data/vendor/assets/javascripts/codemirror/modes/php.js +3 -0
  35. data/vendor/assets/javascripts/codemirror/modes/python.js +2 -1
  36. data/vendor/assets/javascripts/codemirror/modes/ruby.js +2 -1
  37. data/vendor/assets/javascripts/codemirror/modes/rust.js +4 -1
  38. data/vendor/assets/javascripts/codemirror/modes/sass.js +8 -27
  39. data/vendor/assets/javascripts/codemirror/modes/scheme.js +3 -1
  40. data/vendor/assets/javascripts/codemirror/modes/smalltalk.js +6 -4
  41. data/vendor/assets/javascripts/codemirror/modes/sql.js +3 -4
  42. data/vendor/assets/javascripts/codemirror/modes/stex.js +1 -1
  43. data/vendor/assets/javascripts/codemirror/modes/xml.js +2 -0
  44. data/vendor/assets/javascripts/codemirror/modes/yaml.js +3 -1
  45. data/vendor/assets/stylesheets/codemirror.css +7 -4
  46. data/vendor/assets/stylesheets/codemirror/addons/lint/lint.css +3 -3
  47. data/vendor/assets/stylesheets/codemirror/themes/rubyblue.css +1 -1
  48. metadata +14 -6
  49. data/vendor/assets/javascripts/codemirror/modes/test.js +0 -64
@@ -58,36 +58,36 @@
58
58
  var defaultKeymap = [
59
59
  // Key to key mapping. This goes first to make it possible to override
60
60
  // existing mappings.
61
- { keys: ['Left'], type: 'keyToKey', toKeys: ['h'] },
62
- { keys: ['Right'], type: 'keyToKey', toKeys: ['l'] },
63
- { keys: ['Up'], type: 'keyToKey', toKeys: ['k'] },
64
- { keys: ['Down'], type: 'keyToKey', toKeys: ['j'] },
65
- { keys: ['Space'], type: 'keyToKey', toKeys: ['l'] },
66
- { keys: ['Backspace'], type: 'keyToKey', toKeys: ['h'] },
67
- { keys: ['Ctrl-Space'], type: 'keyToKey', toKeys: ['W'] },
68
- { keys: ['Ctrl-Backspace'], type: 'keyToKey', toKeys: ['B'] },
69
- { keys: ['Shift-Space'], type: 'keyToKey', toKeys: ['w'] },
70
- { keys: ['Shift-Backspace'], type: 'keyToKey', toKeys: ['b'] },
71
- { keys: ['Ctrl-n'], type: 'keyToKey', toKeys: ['j'] },
72
- { keys: ['Ctrl-p'], type: 'keyToKey', toKeys: ['k'] },
73
- { keys: ['Ctrl-['], type: 'keyToKey', toKeys: ['Esc'] },
74
- { keys: ['Ctrl-c'], type: 'keyToKey', toKeys: ['Esc'] },
61
+ { keys: ['<Left>'], type: 'keyToKey', toKeys: ['h'] },
62
+ { keys: ['<Right>'], type: 'keyToKey', toKeys: ['l'] },
63
+ { keys: ['<Up>'], type: 'keyToKey', toKeys: ['k'] },
64
+ { keys: ['<Down>'], type: 'keyToKey', toKeys: ['j'] },
65
+ { keys: ['<Space>'], type: 'keyToKey', toKeys: ['l'] },
66
+ { keys: ['<BS>'], type: 'keyToKey', toKeys: ['h'] },
67
+ { keys: ['<C-Space>'], type: 'keyToKey', toKeys: ['W'] },
68
+ { keys: ['<C-BS>'], type: 'keyToKey', toKeys: ['B'] },
69
+ { keys: ['<S-Space>'], type: 'keyToKey', toKeys: ['w'] },
70
+ { keys: ['<S-BS>'], type: 'keyToKey', toKeys: ['b'] },
71
+ { keys: ['<C-n>'], type: 'keyToKey', toKeys: ['j'] },
72
+ { keys: ['<C-p>'], type: 'keyToKey', toKeys: ['k'] },
73
+ { keys: ['C-['], type: 'keyToKey', toKeys: ['<Esc>'] },
74
+ { keys: ['<C-c>'], type: 'keyToKey', toKeys: ['<Esc>'] },
75
75
  { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'] },
76
76
  { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'] },
77
- { keys: ['Home'], type: 'keyToKey', toKeys: ['0'] },
78
- { keys: ['End'], type: 'keyToKey', toKeys: ['$'] },
79
- { keys: ['PageUp'], type: 'keyToKey', toKeys: ['Ctrl-b'] },
80
- { keys: ['PageDown'], type: 'keyToKey', toKeys: ['Ctrl-f'] },
77
+ { keys: ['<Home>'], type: 'keyToKey', toKeys: ['0'] },
78
+ { keys: ['<End>'], type: 'keyToKey', toKeys: ['$'] },
79
+ { keys: ['<PageUp>'], type: 'keyToKey', toKeys: ['<C-b>'] },
80
+ { keys: ['<PageDown>'], type: 'keyToKey', toKeys: ['<C-f>'] },
81
81
  // Motions
82
82
  { keys: ['H'], type: 'motion',
83
83
  motion: 'moveToTopLine',
84
- motionArgs: { linewise: true }},
84
+ motionArgs: { linewise: true, toJumplist: true }},
85
85
  { keys: ['M'], type: 'motion',
86
86
  motion: 'moveToMiddleLine',
87
- motionArgs: { linewise: true }},
87
+ motionArgs: { linewise: true, toJumplist: true }},
88
88
  { keys: ['L'], type: 'motion',
89
89
  motion: 'moveToBottomLine',
90
- motionArgs: { linewise: true }},
90
+ motionArgs: { linewise: true, toJumplist: true }},
91
91
  { keys: ['h'], type: 'motion',
92
92
  motion: 'moveByCharacters',
93
93
  motionArgs: { forward: false }},
@@ -133,25 +133,25 @@
133
133
  motionArgs: { forward: false, wordEnd: true, bigWord: true,
134
134
  inclusive: true }},
135
135
  { keys: ['{'], type: 'motion', motion: 'moveByParagraph',
136
- motionArgs: { forward: false }},
136
+ motionArgs: { forward: false, toJumplist: true }},
137
137
  { keys: ['}'], type: 'motion', motion: 'moveByParagraph',
138
- motionArgs: { forward: true }},
139
- { keys: ['Ctrl-f'], type: 'motion',
138
+ motionArgs: { forward: true, toJumplist: true }},
139
+ { keys: ['<C-f>'], type: 'motion',
140
140
  motion: 'moveByPage', motionArgs: { forward: true }},
141
- { keys: ['Ctrl-b'], type: 'motion',
141
+ { keys: ['<C-b>'], type: 'motion',
142
142
  motion: 'moveByPage', motionArgs: { forward: false }},
143
- { keys: ['Ctrl-d'], type: 'motion',
143
+ { keys: ['<C-d>'], type: 'motion',
144
144
  motion: 'moveByScroll',
145
145
  motionArgs: { forward: true, explicitRepeat: true }},
146
- { keys: ['Ctrl-u'], type: 'motion',
146
+ { keys: ['<C-u>'], type: 'motion',
147
147
  motion: 'moveByScroll',
148
148
  motionArgs: { forward: false, explicitRepeat: true }},
149
149
  { keys: ['g', 'g'], type: 'motion',
150
150
  motion: 'moveToLineOrEdgeOfDocument',
151
- motionArgs: { forward: false, explicitRepeat: true, linewise: true }},
151
+ motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
152
152
  { keys: ['G'], type: 'motion',
153
153
  motion: 'moveToLineOrEdgeOfDocument',
154
- motionArgs: { forward: true, explicitRepeat: true, linewise: true }},
154
+ motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
155
155
  { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' },
156
156
  { keys: ['^'], type: 'motion',
157
157
  motion: 'moveToFirstNonWhiteSpaceCharacter' },
@@ -169,7 +169,7 @@
169
169
  motionArgs: { inclusive: true }},
170
170
  { keys: ['%'], type: 'motion',
171
171
  motion: 'moveToMatchedSymbol',
172
- motionArgs: { inclusive: true }},
172
+ motionArgs: { inclusive: true, toJumplist: true }},
173
173
  { keys: ['f', 'character'], type: 'motion',
174
174
  motion: 'moveToCharacter',
175
175
  motionArgs: { forward: true , inclusive: true }},
@@ -186,18 +186,20 @@
186
186
  motionArgs: { forward: true }},
187
187
  { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch',
188
188
  motionArgs: { forward: false }},
189
- { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark' },
190
- { keys: ['`', 'character'], type: 'motion', motion: 'goToMark' },
189
+ { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark',
190
+ motionArgs: {toJumplist: true}},
191
+ { keys: ['`', 'character'], type: 'motion', motion: 'goToMark',
192
+ motionArgs: {toJumplist: true}},
191
193
  { keys: [']', '`',], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
192
194
  { keys: ['[', '`',], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
193
195
  { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
194
196
  { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
195
197
  { keys: [']', 'character'], type: 'motion',
196
198
  motion: 'moveToSymbol',
197
- motionArgs: { forward: true}},
199
+ motionArgs: { forward: true, toJumplist: true}},
198
200
  { keys: ['[', 'character'], type: 'motion',
199
201
  motion: 'moveToSymbol',
200
- motionArgs: { forward: false}},
202
+ motionArgs: { forward: false, toJumplist: true}},
201
203
  { keys: ['|'], type: 'motion',
202
204
  motion: 'moveToColumn',
203
205
  motionArgs: { }},
@@ -212,9 +214,9 @@
212
214
  operatorArgs: { indentRight: false }},
213
215
  { keys: ['g', '~'], type: 'operator', operator: 'swapcase' },
214
216
  { keys: ['n'], type: 'motion', motion: 'findNext',
215
- motionArgs: { forward: true }},
217
+ motionArgs: { forward: true, toJumplist: true }},
216
218
  { keys: ['N'], type: 'motion', motion: 'findNext',
217
- motionArgs: { forward: false }},
219
+ motionArgs: { forward: false, toJumplist: true }},
218
220
  // Operator-Motion dual commands
219
221
  { keys: ['x'], type: 'operatorMotion', operator: 'delete',
220
222
  motion: 'moveByCharacters', motionArgs: { forward: true },
@@ -235,13 +237,18 @@
235
237
  { keys: ['~'], type: 'operatorMotion', operator: 'swapcase',
236
238
  motion: 'moveByCharacters', motionArgs: { forward: true }},
237
239
  // Actions
240
+ { keys: ['<C-i>'], type: 'action', action: 'jumpListWalk',
241
+ actionArgs: { forward: true }},
242
+ { keys: ['<C-o>'], type: 'action', action: 'jumpListWalk',
243
+ actionArgs: { forward: false }},
238
244
  { keys: ['a'], type: 'action', action: 'enterInsertMode',
239
245
  actionArgs: { insertAt: 'charAfter' }},
240
246
  { keys: ['A'], type: 'action', action: 'enterInsertMode',
241
247
  actionArgs: { insertAt: 'eol' }},
242
- { keys: ['i'], type: 'action', action: 'enterInsertMode' },
248
+ { keys: ['i'], type: 'action', action: 'enterInsertMode',
249
+ actionArgs: { insertAt: 'inplace' }},
243
250
  { keys: ['I'], type: 'action', action: 'enterInsertMode',
244
- motion: 'moveToFirstNonWhiteSpaceCharacter' },
251
+ actionArgs: { insertAt: 'firstNonBlank' }},
245
252
  { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode',
246
253
  actionArgs: { after: true }},
247
254
  { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode',
@@ -255,8 +262,11 @@
255
262
  { keys: ['P'], type: 'action', action: 'paste',
256
263
  actionArgs: { after: false }},
257
264
  { keys: ['r', 'character'], type: 'action', action: 'replace' },
265
+ { keys: ['@', 'character'], type: 'action', action: 'replayMacro' },
266
+ { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' },
267
+ { keys: ['R'], type: 'action', action: 'enterReplaceMode' },
258
268
  { keys: ['u'], type: 'action', action: 'undo' },
259
- { keys: ['Ctrl-r'], type: 'action', action: 'redo' },
269
+ { keys: ['<C-r>'], type: 'action', action: 'redo' },
260
270
  { keys: ['m', 'character'], type: 'action', action: 'setMark' },
261
271
  { keys: ['\"', 'character'], type: 'action', action: 'setRegister' },
262
272
  { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor',
@@ -266,7 +276,7 @@
266
276
  motion: 'moveToFirstNonWhiteSpaceCharacter' },
267
277
  { keys: ['z', 't'], type: 'action', action: 'scrollToCursor',
268
278
  actionArgs: { position: 'top' }},
269
- { keys: ['z', 'Enter'], type: 'action', action: 'scrollToCursor',
279
+ { keys: ['z', '<CR>'], type: 'action', action: 'scrollToCursor',
270
280
  actionArgs: { position: 'top' },
271
281
  motion: 'moveToFirstNonWhiteSpaceCharacter' },
272
282
  { keys: ['z', '-'], type: 'action', action: 'scrollToCursor',
@@ -275,9 +285,9 @@
275
285
  actionArgs: { position: 'bottom' },
276
286
  motion: 'moveToFirstNonWhiteSpaceCharacter' },
277
287
  { keys: ['.'], type: 'action', action: 'repeatLastEdit' },
278
- { keys: ['Ctrl-a'], type: 'action', action: 'incrementNumberToken',
288
+ { keys: ['<C-a>'], type: 'action', action: 'incrementNumberToken',
279
289
  actionArgs: {increase: true, backtrack: false}},
280
- { keys: ['Ctrl-x'], type: 'action', action: 'incrementNumberToken',
290
+ { keys: ['<C-x>'], type: 'action', action: 'incrementNumberToken',
281
291
  actionArgs: {increase: false, backtrack: false}},
282
292
  // Text object motions
283
293
  { keys: ['a', 'character'], type: 'motion',
@@ -287,13 +297,13 @@
287
297
  motionArgs: { textObjectInner: true }},
288
298
  // Search
289
299
  { keys: ['/'], type: 'search',
290
- searchArgs: { forward: true, querySrc: 'prompt' }},
300
+ searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
291
301
  { keys: ['?'], type: 'search',
292
- searchArgs: { forward: false, querySrc: 'prompt' }},
302
+ searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
293
303
  { keys: ['*'], type: 'search',
294
- searchArgs: { forward: true, querySrc: 'wordUnderCursor' }},
304
+ searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
295
305
  { keys: ['#'], type: 'search',
296
- searchArgs: { forward: false, querySrc: 'wordUnderCursor' }},
306
+ searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
297
307
  // Ex command
298
308
  { keys: [':'], type: 'ex' }
299
309
  ];
@@ -361,6 +371,91 @@
361
371
  return false;
362
372
  }
363
373
 
374
+ var createCircularJumpList = function() {
375
+ var size = 100;
376
+ var pointer = -1;
377
+ var head = 0;
378
+ var tail = 0;
379
+ var buffer = new Array(size);
380
+ function add(cm, oldCur, newCur) {
381
+ var current = pointer % size;
382
+ var curMark = buffer[current];
383
+ function useNextSlot(cursor) {
384
+ var next = ++pointer % size;
385
+ var trashMark = buffer[next];
386
+ if (trashMark) {
387
+ trashMark.clear();
388
+ }
389
+ buffer[next] = cm.setBookmark(cursor);
390
+ }
391
+ if (curMark) {
392
+ var markPos = curMark.find();
393
+ // avoid recording redundant cursor position
394
+ if (markPos && !cursorEqual(markPos, oldCur)) {
395
+ useNextSlot(oldCur);
396
+ }
397
+ } else {
398
+ useNextSlot(oldCur);
399
+ }
400
+ useNextSlot(newCur);
401
+ head = pointer;
402
+ tail = pointer - size + 1;
403
+ if (tail < 0) {
404
+ tail = 0;
405
+ }
406
+ }
407
+ function move(cm, offset) {
408
+ pointer += offset;
409
+ if (pointer > head) {
410
+ pointer = head;
411
+ } else if (pointer < tail) {
412
+ pointer = tail;
413
+ }
414
+ var mark = buffer[(size + pointer) % size];
415
+ // skip marks that are temporarily removed from text buffer
416
+ if (mark && !mark.find()) {
417
+ var inc = offset > 0 ? 1 : -1;
418
+ var newCur;
419
+ var oldCur = cm.getCursor();
420
+ do {
421
+ pointer += inc;
422
+ mark = buffer[(size + pointer) % size];
423
+ // skip marks that are the same as current position
424
+ if (mark &&
425
+ (newCur = mark.find()) &&
426
+ !cursorEqual(oldCur, newCur)) {
427
+ break;
428
+ }
429
+ } while (pointer < head && pointer > tail);
430
+ }
431
+ return mark;
432
+ }
433
+ return {
434
+ cachedCursor: undefined, //used for # and * jumps
435
+ add: add,
436
+ move: move
437
+ };
438
+ };
439
+
440
+ var createMacroState = function() {
441
+ return {
442
+ macroKeyBuffer: [],
443
+ latestRegister: undefined,
444
+ enteredMacroMode: undefined,
445
+ isMacroPlaying: false,
446
+ toggle: function(cm, registerName) {
447
+ if (this.enteredMacroMode) { //onExit
448
+ this.enteredMacroMode(); // close dialog
449
+ this.enteredMacroMode = undefined;
450
+ } else { //onEnter
451
+ this.latestRegister = registerName;
452
+ this.enteredMacroMode = cm.openDialog(
453
+ '(recording)['+registerName+']', null, {bottom:true});
454
+ }
455
+ }
456
+ }
457
+ }
458
+
364
459
  // Global Vim state. Call getVimGlobalState to get and initialize.
365
460
  var vimGlobalState;
366
461
  function getVimGlobalState() {
@@ -370,6 +465,8 @@
370
465
  searchQuery: null,
371
466
  // Whether we are searching backwards.
372
467
  searchIsReversed: false,
468
+ jumpList: createCircularJumpList(),
469
+ macroModeState: createMacroState(),
373
470
  // Recording latest f, t, F or T motion command.
374
471
  lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
375
472
  registerController: new RegisterController({})
@@ -435,7 +532,15 @@
435
532
  handleKey: function(cm, key) {
436
533
  var command;
437
534
  var vim = getVimState(cm);
438
- if (key == 'Esc') {
535
+ var macroModeState = getVimGlobalState().macroModeState;
536
+ if (macroModeState.enteredMacroMode) {
537
+ if (key == 'q') {
538
+ actions.exitMacroRecordMode();
539
+ return;
540
+ }
541
+ logKey(macroModeState, key);
542
+ }
543
+ if (key == '<Esc>') {
439
544
  // Clear input state and get back to normal mode.
440
545
  vim.inputState = new InputState();
441
546
  if (vim.visualMode) {
@@ -597,6 +702,9 @@
597
702
  this.unamedRegister.set(text, linewise);
598
703
  }
599
704
  },
705
+ setRegisterText: function(name, text, linewise) {
706
+ this.getRegister(name).set(text, linewise);
707
+ },
600
708
  // Gets the register named @name. If one of @name doesn't already exist,
601
709
  // create it. If @name is invalid, return the unamedRegister.
602
710
  getRegister: function(name) {
@@ -643,10 +751,10 @@
643
751
  inputState.selectedCharacter = keys[keys.length - 1];
644
752
  if(inputState.selectedCharacter.length>1){
645
753
  switch(inputState.selectedCharacter){
646
- case "Enter":
754
+ case "<CR>":
647
755
  inputState.selectedCharacter='\n';
648
756
  break;
649
- case "Space":
757
+ case "<Space>":
650
758
  inputState.selectedCharacter=' ';
651
759
  break;
652
760
  default:
@@ -769,13 +877,13 @@
769
877
  try {
770
878
  updateSearchQuery(cm, query, ignoreCase, smartCase);
771
879
  } catch (e) {
772
- showConfirm(cm, 'Invalid regex: ' + regexPart);
880
+ showConfirm(cm, 'Invalid regex: ' + query);
773
881
  return;
774
882
  }
775
883
  commandDispatcher.processMotion(cm, vim, {
776
884
  type: 'motion',
777
885
  motion: 'findNext',
778
- motionArgs: { forward: true }
886
+ motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }
779
887
  });
780
888
  }
781
889
  function onPromptClose(query) {
@@ -834,13 +942,19 @@
834
942
  return;
835
943
  }
836
944
  var query = cm.getLine(word.start.line).substring(word.start.ch,
837
- word.end.ch + 1);
945
+ word.end.ch);
838
946
  if (isKeyword) {
839
947
  query = '\\b' + query + '\\b';
840
948
  } else {
841
949
  query = escapeRegex(query);
842
950
  }
951
+
952
+ // cachedCursor is used to save the old position of the cursor
953
+ // when * or # causes vim to seek for the nearest word and shift
954
+ // the cursor before entering the motion.
955
+ getVimGlobalState().jumpList.cachedCursor = cm.getCursor();
843
956
  cm.setCursor(word.start);
957
+
844
958
  handleQuery(query, true /** ignoreCase */, false /** smartCase */);
845
959
  break;
846
960
  }
@@ -917,6 +1031,17 @@
917
1031
  if (!motionResult) {
918
1032
  return;
919
1033
  }
1034
+ if (motionArgs.toJumplist) {
1035
+ var jumpList = getVimGlobalState().jumpList;
1036
+ // if the current motion is # or *, use cachedCursor
1037
+ var cachedCursor = jumpList.cachedCursor;
1038
+ if (cachedCursor) {
1039
+ recordJumpPosition(cm, cachedCursor, motionResult);
1040
+ delete jumpList.cachedCursor;
1041
+ } else {
1042
+ recordJumpPosition(cm, curOriginal, motionResult);
1043
+ }
1044
+ }
920
1045
  if (motionResult instanceof Array) {
921
1046
  curStart = motionResult[0];
922
1047
  curEnd = motionResult[1];
@@ -992,7 +1117,7 @@
992
1117
  // Expand selection to entire line.
993
1118
  expandSelectionToLine(cm, curStart, curEnd);
994
1119
  } else if (motionArgs.forward) {
995
- // Clip to trailing newlines only if we the motion goes forward.
1120
+ // Clip to trailing newlines only if the motion goes forward.
996
1121
  clipToLine(cm, curStart, curEnd);
997
1122
  }
998
1123
  operatorArgs.registerName = registerName;
@@ -1077,7 +1202,7 @@
1077
1202
  }
1078
1203
 
1079
1204
  var equal = cursorEqual(cursor, best);
1080
- var between = (motionArgs.forward) ?
1205
+ var between = (motionArgs.forward) ?
1081
1206
  cusrorIsBetween(cursor, mark, best) :
1082
1207
  cusrorIsBetween(best, mark, cursor);
1083
1208
 
@@ -1255,9 +1380,24 @@
1255
1380
  },
1256
1381
  moveToMatchedSymbol: function(cm, motionArgs) {
1257
1382
  var cursor = cm.getCursor();
1258
- var symbol = cm.getLine(cursor.line).charAt(cursor.ch);
1259
- if (isMatchableSymbol(symbol)) {
1260
- return findMatchedSymbol(cm, cm.getCursor(), motionArgs.symbol);
1383
+ var line = cursor.line;
1384
+ var ch = cursor.ch;
1385
+ var lineText = cm.getLine(line);
1386
+ var symbol;
1387
+ var startContext = cm.getTokenAt(cursor).type;
1388
+ var startCtxLevel = getContextLevel(startContext);
1389
+ do {
1390
+ symbol = lineText.charAt(ch++);
1391
+ if (symbol && isMatchableSymbol(symbol)) {
1392
+ var endContext = cm.getTokenAt({line:line, ch:ch}).type;
1393
+ var endCtxLevel = getContextLevel(endContext);
1394
+ if (startCtxLevel >= endCtxLevel) {
1395
+ break;
1396
+ }
1397
+ }
1398
+ } while (symbol);
1399
+ if (symbol) {
1400
+ return findMatchedSymbol(cm, {line:line, ch:ch-1}, symbol);
1261
1401
  } else {
1262
1402
  return cursor;
1263
1403
  }
@@ -1383,24 +1523,58 @@
1383
1523
  };
1384
1524
 
1385
1525
  var actions = {
1526
+ jumpListWalk: function(cm, actionArgs, vim) {
1527
+ if (vim.visualMode) {
1528
+ return;
1529
+ }
1530
+ var repeat = actionArgs.repeat;
1531
+ var forward = actionArgs.forward;
1532
+ var jumpList = getVimGlobalState().jumpList;
1533
+
1534
+ var mark = jumpList.move(cm, forward ? repeat : -repeat);
1535
+ var markPos = mark ? mark.find() : undefined;
1536
+ markPos = markPos ? markPos : cm.getCursor();
1537
+ cm.setCursor(markPos);
1538
+ },
1386
1539
  scrollToCursor: function(cm, actionArgs) {
1387
1540
  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;
1541
+ var charCoords = cm.charCoords({line: lineNum, ch: 0}, "local");
1542
+ var height = cm.getScrollInfo().clientHeight;
1543
+ var y = charCoords.top;
1544
+ var lineHeight = charCoords.bottom - y;
1393
1545
  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;
1546
+ case 'center': y = y - (height / 2) + lineHeight;
1547
+ break;
1548
+ case 'bottom': y = y - height + lineHeight*1.4;
1549
+ break;
1550
+ case 'top': y = y + lineHeight*0.4;
1551
+ break;
1399
1552
  }
1400
1553
  cm.scrollTo(null, y);
1401
- // The calculations are slightly off, use scrollIntoView to nudge the
1402
- // view into the right place.
1403
- cm.scrollIntoView();
1554
+ },
1555
+ replayMacro: function(cm, actionArgs) {
1556
+ var registerName = actionArgs.selectedCharacter;
1557
+ var repeat = actionArgs.repeat;
1558
+ var macroModeState = getVimGlobalState().macroModeState;
1559
+ if (registerName == '@') {
1560
+ registerName = macroModeState.latestRegister;
1561
+ }
1562
+ var keyBuffer = parseRegisterToKeyBuffer(macroModeState, registerName);
1563
+ while(repeat--){
1564
+ executeMacroKeyBuffer(cm, macroModeState, keyBuffer);
1565
+ }
1566
+ },
1567
+ exitMacroRecordMode: function(cm, actionArgs) {
1568
+ var macroModeState = getVimGlobalState().macroModeState;
1569
+ macroModeState.toggle();
1570
+ parseKeyBufferToRegister(macroModeState.latestRegister,
1571
+ macroModeState.macroKeyBuffer);
1572
+ },
1573
+ enterMacroRecordMode: function(cm, actionArgs) {
1574
+ var macroModeState = getVimGlobalState().macroModeState;
1575
+ var registerName = actionArgs.selectedCharacter;
1576
+ macroModeState.toggle(cm, registerName);
1577
+ emptyMacroKeyBuffer(macroModeState);
1404
1578
  },
1405
1579
  enterInsertMode: function(cm, actionArgs) {
1406
1580
  var insertAt = (actionArgs) ? actionArgs.insertAt : null;
@@ -1410,6 +1584,8 @@
1410
1584
  cm.setCursor(cursor);
1411
1585
  } else if (insertAt == 'charAfter') {
1412
1586
  cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
1587
+ } else if (insertAt == 'firstNonBlank') {
1588
+ cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1413
1589
  }
1414
1590
  cm.setOption('keyMap', 'vim-insert');
1415
1591
  },
@@ -1605,6 +1781,10 @@
1605
1781
  }
1606
1782
  }
1607
1783
  },
1784
+ enterReplaceMode: function(cm, actionArgs) {
1785
+ cm.setOption('keyMap', 'vim-replace');
1786
+ cm.toggleOverwrite();
1787
+ },
1608
1788
  incrementNumberToken: function(cm, actionArgs, vim) {
1609
1789
  var cur = cm.getCursor();
1610
1790
  var lineStr = cm.getLine(cur.line);
@@ -1804,10 +1984,30 @@
1804
1984
  // caret to the first character of the next line.
1805
1985
  function clipToLine(cm, curStart, curEnd) {
1806
1986
  var selection = cm.getRange(curStart, curEnd);
1807
- var lines = selection.split('\n');
1808
- if (lines.length > 1 && isWhiteSpaceString(lines.pop())) {
1809
- curEnd.line--;
1810
- curEnd.ch = lineLength(cm, curEnd.line);
1987
+ // Only clip if the selection ends with trailing newline + whitespace
1988
+ if (/\n\s*$/.test(selection)) {
1989
+ var lines = selection.split('\n');
1990
+ // We know this is all whitepsace.
1991
+ lines.pop();
1992
+
1993
+ // Cases:
1994
+ // 1. Last word is an empty line - do not clip the trailing '\n'
1995
+ // 2. Last word is not an empty line - clip the trailing '\n'
1996
+ var line;
1997
+ // Find the line containing the last word, and clip all whitespace up
1998
+ // to it.
1999
+ for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
2000
+ var clipped = false;
2001
+ curEnd.line--;
2002
+ curEnd.ch = 0;
2003
+ }
2004
+ // If the last word is not an empty line, clip an additional newline
2005
+ if (line) {
2006
+ curEnd.line--;
2007
+ curEnd.ch = lineLength(cm, curEnd.line);
2008
+ } else {
2009
+ curEnd.ch = 0;
2010
+ }
1811
2011
  }
1812
2012
  }
1813
2013
 
@@ -1861,21 +2061,39 @@
1861
2061
 
1862
2062
  var wordAfterRegex = matchRegex.exec(textAfterIdx);
1863
2063
  var wordStart = idx;
1864
- var wordEnd = idx + wordAfterRegex[0].length - 1;
2064
+ var wordEnd = idx + wordAfterRegex[0].length;
1865
2065
  // TODO: Find a better way to do this. It will be slow on very long lines.
1866
- var wordBeforeRegex = matchRegex.exec(reverse(textBeforeIdx));
2066
+ var revTextBeforeIdx = reverse(textBeforeIdx);
2067
+ var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx);
1867
2068
  if (wordBeforeRegex) {
1868
2069
  wordStart -= wordBeforeRegex[0].length;
1869
2070
  }
1870
2071
 
1871
2072
  if (inclusive) {
1872
- wordEnd++;
2073
+ // If present, trim all whitespace after word.
2074
+ // Otherwise, trim all whitespace before word.
2075
+ var textAfterWordEnd = line.substring(wordEnd);
2076
+ var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length;
2077
+ if (whitespacesAfterWord > 0) {
2078
+ wordEnd += whitespacesAfterWord;
2079
+ } else {
2080
+ var revTrim = revTextBeforeIdx.length - wordStart;
2081
+ var textBeforeWordStart = revTextBeforeIdx.substring(revTrim);
2082
+ var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length;
2083
+ wordStart -= whitespacesBeforeWord;
2084
+ }
1873
2085
  }
1874
2086
 
1875
2087
  return { start: { line: cur.line, ch: wordStart },
1876
2088
  end: { line: cur.line, ch: wordEnd }};
1877
2089
  }
1878
2090
 
2091
+ function recordJumpPosition(cm, oldCur, newCur) {
2092
+ if(!cursorEqual(oldCur, newCur)) {
2093
+ getVimGlobalState().jumpList.add(cm, oldCur, newCur);
2094
+ }
2095
+ }
2096
+
1879
2097
  function recordLastCharacterSearch(increment, args) {
1880
2098
  var vimGlobalState = getVimGlobalState();
1881
2099
  vimGlobalState.lastChararacterSearch.increment = increment;
@@ -2016,18 +2234,31 @@
2016
2234
  * backward.
2017
2235
  * @param {boolean} bigWord True if punctuation count as part of the word.
2018
2236
  * False if only [a-zA-Z0-9] characters count as part of the word.
2237
+ * @param {boolean} emptyLineIsWord True if empty lines should be treated
2238
+ * as words.
2019
2239
  * @return {Object{from:number, to:number, line: number}} The boundaries of
2020
2240
  * the word, or null if there are no more words.
2021
2241
  */
2022
- // TODO: Treat empty lines (with no whitespace) as words.
2023
- function findWord(cm, cur, forward, bigWord) {
2242
+ function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {
2024
2243
  var lineNum = cur.line;
2025
2244
  var pos = cur.ch;
2026
2245
  var line = cm.getLine(lineNum);
2027
2246
  var dir = forward ? 1 : -1;
2028
2247
  var regexps = bigWord ? bigWordRegexp : wordRegexp;
2029
2248
 
2249
+ if (emptyLineIsWord && line == '') {
2250
+ lineNum += dir;
2251
+ line = cm.getLine(lineNum);
2252
+ if (!isLine(cm, lineNum)) {
2253
+ return null;
2254
+ }
2255
+ pos = (forward) ? 0 : line.length;
2256
+ }
2257
+
2030
2258
  while (true) {
2259
+ if (emptyLineIsWord && line == '') {
2260
+ return { from: 0, to: 0, line: lineNum };
2261
+ }
2031
2262
  var stop = (dir > 0) ? line.length : -1;
2032
2263
  var wordStart = stop, wordEnd = stop;
2033
2264
  // Find bounds of next word.
@@ -2083,56 +2314,48 @@
2083
2314
  */
2084
2315
  function moveToWord(cm, repeat, forward, wordEnd, bigWord) {
2085
2316
  var cur = cm.getCursor();
2317
+ var curStart = copyCursor(cur);
2318
+ var words = [];
2319
+ if (forward && !wordEnd || !forward && wordEnd) {
2320
+ repeat++;
2321
+ }
2322
+ // For 'e', empty lines are not considered words, go figure.
2323
+ var emptyLineIsWord = !(forward && wordEnd);
2086
2324
  for (var i = 0; i < repeat; i++) {
2087
- var startCh = cur.ch, startLine = cur.line, word;
2088
- var movedToNextWord = false;
2089
- while (!movedToNextWord) {
2090
- // Search and advance.
2091
- word = findWord(cm, cur, forward, bigWord);
2092
- movedToNextWord = true;
2093
- if (word) {
2094
- // Move to the word we just found. If by moving to the word we end
2095
- // up in the same spot, then move an extra character and search
2096
- // again.
2097
- cur.line = word.line;
2098
- if (forward && wordEnd) {
2099
- // 'e'
2100
- cur.ch = word.to - 1;
2101
- } else if (forward && !wordEnd) {
2102
- // 'w'
2103
- if (inRangeInclusive(cur.ch, word.from, word.to) &&
2104
- word.line == startLine) {
2105
- // Still on the same word. Go to the next one.
2106
- movedToNextWord = false;
2107
- cur.ch = word.to - 1;
2108
- } else {
2109
- cur.ch = word.from;
2110
- }
2111
- } else if (!forward && wordEnd) {
2112
- // 'ge'
2113
- if (inRangeInclusive(cur.ch, word.from, word.to) &&
2114
- word.line == startLine) {
2115
- // still on the same word. Go to the next one.
2116
- movedToNextWord = false;
2117
- cur.ch = word.from;
2118
- } else {
2119
- cur.ch = word.to;
2120
- }
2121
- } else if (!forward && !wordEnd) {
2122
- // 'b'
2123
- cur.ch = word.from;
2124
- }
2125
- } else {
2126
- // No more words to be found. Move to the end.
2127
- if (forward) {
2128
- return { line: cur.line, ch: lineLength(cm, cur.line) };
2129
- } else {
2130
- return { line: cur.line, ch: 0 };
2131
- }
2132
- }
2133
- }
2325
+ var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);
2326
+ if (!word) {
2327
+ var eodCh = lineLength(cm, cm.lastLine());
2328
+ words.push(forward
2329
+ ? {line: cm.lastLine(), from: eodCh, to: eodCh}
2330
+ : {line: 0, from: 0, to: 0});
2331
+ break;
2332
+ }
2333
+ words.push(word);
2334
+ cur = {line: word.line, ch: forward ? (word.to - 1) : word.from};
2335
+ }
2336
+ var shortCircuit = words.length != repeat;
2337
+ var firstWord = words[0];
2338
+ var lastWord = words.pop();
2339
+ if (forward && !wordEnd) {
2340
+ // w
2341
+ if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {
2342
+ // We did not start in the middle of a word. Discard the extra word at the end.
2343
+ lastWord = words.pop();
2344
+ }
2345
+ return {line: lastWord.line, ch: lastWord.from};
2346
+ } else if (forward && wordEnd) {
2347
+ return {line: lastWord.line, ch: lastWord.to - 1};
2348
+ } else if (!forward && wordEnd) {
2349
+ // ge
2350
+ if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {
2351
+ // We did not start in the middle of a word. Discard the extra word at the end.
2352
+ lastWord = words.pop();
2353
+ }
2354
+ return {line: lastWord.line, ch: lastWord.to};
2355
+ } else {
2356
+ // b
2357
+ return {line: lastWord.line, ch: lastWord.from};
2134
2358
  }
2135
- return cur;
2136
2359
  }
2137
2360
 
2138
2361
  function moveToCharacter(cm, repeat, forward, character) {
@@ -2188,9 +2411,17 @@
2188
2411
  return idx;
2189
2412
  }
2190
2413
 
2414
+ function getContextLevel(ctx) {
2415
+ return (ctx === 'string' || ctx === 'comment') ? 1 : 0;
2416
+ }
2417
+
2191
2418
  function findMatchedSymbol(cm, cur, symb) {
2192
2419
  var line = cur.line;
2193
- symb = symb ? symb : cm.getLine(line).charAt(cur.ch);
2420
+ var ch = cur.ch;
2421
+ symb = symb ? symb : cm.getLine(line).charAt(ch);
2422
+
2423
+ var symbContext = cm.getTokenAt({line:line, ch:ch+1}).type;
2424
+ var symbCtxLevel = getContextLevel(symbContext);
2194
2425
 
2195
2426
  var reverseSymb = ({
2196
2427
  '(': ')', ')': '(',
@@ -2206,7 +2437,7 @@
2206
2437
  // depending on which bracket we're matching
2207
2438
  var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1;
2208
2439
  var endLine = increment === 1 ? cm.lineCount() : -1;
2209
- var depth = 1, nextCh = symb, index = cur.ch, lineText = cm.getLine(line);
2440
+ var depth = 1, nextCh = symb, index = ch, lineText = cm.getLine(line);
2210
2441
  // Simple search for closing paren--just count openings and closings till
2211
2442
  // we find our match
2212
2443
  // TODO: use info from CodeMirror to ignore closing brackets in comments
@@ -2225,10 +2456,14 @@
2225
2456
  }
2226
2457
  nextCh = lineText.charAt(index);
2227
2458
  }
2228
- if (nextCh === symb) {
2229
- depth++;
2230
- } else if (nextCh === reverseSymb) {
2231
- depth--;
2459
+ var revSymbContext = cm.getTokenAt({line:line, ch:index+1}).type;
2460
+ var revSymbCtxLevel = getContextLevel(revSymbContext);
2461
+ if (symbCtxLevel >= revSymbCtxLevel) {
2462
+ if (nextCh === symb) {
2463
+ depth++;
2464
+ } else if (nextCh === reverseSymb) {
2465
+ depth--;
2466
+ }
2232
2467
  }
2233
2468
  }
2234
2469
 
@@ -2352,7 +2587,7 @@
2352
2587
  onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp });
2353
2588
  }
2354
2589
  else {
2355
- callback(prompt(shortText, ""));
2590
+ onClose(prompt(shortText, ""));
2356
2591
  }
2357
2592
  }
2358
2593
  function findUnescapedSlashes(str) {
@@ -2479,7 +2714,7 @@
2479
2714
  if (match[0].length == 0) {
2480
2715
  // Matched empty string, skip to next.
2481
2716
  stream.next();
2482
- return;
2717
+ return "searching";
2483
2718
  }
2484
2719
  if (!stream.sol()) {
2485
2720
  // Backtrack 1 to match \b
@@ -2515,12 +2750,11 @@
2515
2750
  if (repeat === undefined) { repeat = 1; }
2516
2751
  return cm.operation(function() {
2517
2752
  var pos = cm.getCursor();
2518
- if (!prev) {
2519
- pos.ch += 1;
2520
- }
2521
2753
  var cursor = cm.getSearchCursor(query, pos);
2522
2754
  for (var i = 0; i < repeat; i++) {
2523
- if (!cursor.find(prev)) {
2755
+ var found = cursor.find(prev);
2756
+ if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); }
2757
+ if (!found) {
2524
2758
  // SearchCursor may have returned null because it hit EOF, wrap
2525
2759
  // around and try again.
2526
2760
  cursor = cm.getSearchCursor(query,
@@ -2751,41 +2985,14 @@
2751
2985
  // Converts a key string sequence of the form a<C-w>bd<Left> into Vim's
2752
2986
  // keymap representation.
2753
2987
  function parseKeyString(str) {
2754
- var idx = 0;
2988
+ var key, match;
2755
2989
  var keys = [];
2756
- while (idx < str.length) {
2757
- if (str.charAt(idx) != '<') {
2758
- keys.push(str.charAt(idx));
2759
- idx++;
2760
- continue;
2761
- }
2762
- // Vim key notation here means desktop Vim key-notation.
2763
- // See :help key-notation in desktop Vim.
2764
- var vimKeyNotationStart = ++idx;
2765
- while (str.charAt(idx++) != '>') {}
2766
- var vimKeyNotation = str.substring(vimKeyNotationStart, idx - 1);
2767
- var mod='';
2768
- var match = (/^C-(.+)$/).exec(vimKeyNotation);
2769
- if (match) {
2770
- mod='Ctrl-';
2771
- vimKeyNotation=match[1];
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);
2990
+ while (str) {
2991
+ match = (/<\w+-.+?>|<\w+>|./).exec(str);
2992
+ if(match === null)break;
2993
+ key = match[0];
2994
+ str = str.substring(match.index + key.length);
2995
+ keys.push(key);
2789
2996
  }
2790
2997
  return keys;
2791
2998
  }
@@ -2956,33 +3163,37 @@
2956
3163
  * modifers.
2957
3164
  */
2958
3165
  // TODO: Figure out a way to catch capslock.
2959
- function handleKeyEvent_(cm, key, modifier) {
2960
- if (isUpperCase(key)) {
3166
+ function cmKeyToVimKey(key, modifier) {
3167
+ var vimKey = key;
3168
+ if (isUpperCase(vimKey)) {
2961
3169
  // Convert to lower case if shift is not the modifier since the key
2962
3170
  // we get from CodeMirror is always upper case.
2963
3171
  if (modifier == 'Shift') {
2964
3172
  modifier = null;
2965
3173
  }
2966
3174
  else {
2967
- key = key.toLowerCase();
3175
+ vimKey = vimKey.toLowerCase();
2968
3176
  }
2969
3177
  }
2970
3178
  if (modifier) {
2971
3179
  // Vim will parse modifier+key combination as a single key.
2972
- key = modifier + '-' + key;
3180
+ vimKey = modifier.charAt(0) + '-' + vimKey;
2973
3181
  }
2974
- vim.handleKey(cm, key);
3182
+ var specialKey = ({Enter:'CR',Backspace:'BS',Delete:'Del'})[vimKey];
3183
+ vimKey = specialKey ? specialKey : vimKey;
3184
+ vimKey = vimKey.length > 1 ? '<'+ vimKey + '>' : vimKey;
3185
+ return vimKey;
2975
3186
  }
2976
3187
 
2977
3188
  // Closure to bind CodeMirror, key, modifier.
2978
- function keyMapper(key, modifier) {
3189
+ function keyMapper(vimKey) {
2979
3190
  return function(cm) {
2980
- handleKeyEvent_(cm, key, modifier);
3191
+ vim.handleKey(cm, vimKey);
2981
3192
  };
2982
3193
  }
2983
3194
 
2984
3195
  var modifiers = ['Shift', 'Ctrl'];
2985
- var keyMap = {
3196
+ var cmToVimKeymap = {
2986
3197
  'nofallthrough': true,
2987
3198
  'style': 'fat-cursor'
2988
3199
  };
@@ -2994,11 +3205,9 @@
2994
3205
  // them.
2995
3206
  key = "'" + key + "'";
2996
3207
  }
2997
- if (modifier) {
2998
- keyMap[modifier + '-' + key] = keyMapper(keys[i], modifier);
2999
- } else {
3000
- keyMap[key] = keyMapper(keys[i]);
3001
- }
3208
+ var vimKey = cmKeyToVimKey(keys[i], modifier);
3209
+ var cmKey = modifier ? modifier + '-' + key : key;
3210
+ cmToVimKeymap[cmKey] = keyMapper(vimKey);
3002
3211
  }
3003
3212
  }
3004
3213
  bindKeys(upperCaseAlphabet);
@@ -3010,7 +3219,7 @@
3010
3219
  bindKeys(numbers, 'Ctrl');
3011
3220
  bindKeys(specialKeys);
3012
3221
  bindKeys(specialKeys, 'Ctrl');
3013
- return keyMap;
3222
+ return cmToVimKeymap;
3014
3223
  }
3015
3224
  CodeMirror.keyMap.vim = buildVimKeyMap();
3016
3225
 
@@ -3035,6 +3244,61 @@
3035
3244
  fallthrough: ['default']
3036
3245
  };
3037
3246
 
3247
+ function parseRegisterToKeyBuffer(macroModeState, registerName) {
3248
+ var match, key;
3249
+ var register = getVimGlobalState().registerController.getRegister(registerName);
3250
+ var text = register.toString();
3251
+ var macroKeyBuffer = macroModeState.macroKeyBuffer;
3252
+ emptyMacroKeyBuffer(macroModeState);
3253
+ do {
3254
+ match = text.match(/<\w+-.+>|<\w+>|.|\n/);
3255
+ if(match === null)break;
3256
+ key = match[0];
3257
+ text = text.substring(match.index + key.length);
3258
+ macroKeyBuffer.push(key);
3259
+ } while (text);
3260
+ return macroKeyBuffer;
3261
+ }
3262
+
3263
+ function parseKeyBufferToRegister(registerName, keyBuffer) {
3264
+ var text = keyBuffer.join('');
3265
+ getVimGlobalState().registerController.setRegisterText(registerName, text);
3266
+ }
3267
+
3268
+ function emptyMacroKeyBuffer(macroModeState) {
3269
+ if(macroModeState.isMacroPlaying)return;
3270
+ var macroKeyBuffer = macroModeState.macroKeyBuffer;
3271
+ macroKeyBuffer.length = 0;
3272
+ }
3273
+
3274
+ function executeMacroKeyBuffer(cm, macroModeState, keyBuffer) {
3275
+ macroModeState.isMacroPlaying = true;
3276
+ for (var i = 0, len = keyBuffer.length; i < len; i++) {
3277
+ CodeMirror.Vim.handleKey(cm, keyBuffer[i]);
3278
+ };
3279
+ macroModeState.isMacroPlaying = false;
3280
+ }
3281
+
3282
+ function logKey(macroModeState, key) {
3283
+ if(macroModeState.isMacroPlaying)return;
3284
+ var macroKeyBuffer = macroModeState.macroKeyBuffer;
3285
+ macroKeyBuffer.push(key);
3286
+ }
3287
+
3288
+ function exitReplaceMode(cm) {
3289
+ cm.toggleOverwrite();
3290
+ cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
3291
+ cm.setOption('keyMap', 'vim');
3292
+ }
3293
+
3294
+ CodeMirror.keyMap['vim-replace'] = {
3295
+ 'Esc': exitReplaceMode,
3296
+ 'Ctrl-[': exitReplaceMode,
3297
+ 'Ctrl-C': exitReplaceMode,
3298
+ 'Backspace': 'goCharLeft',
3299
+ fallthrough: ['default']
3300
+ };
3301
+
3038
3302
  return vimApi;
3039
3303
  };
3040
3304
  // Initialize Vim and make it available as an API.