codemirror-rails 3.12 → 3.13

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