|
17 | 17 | // Entering insert mode: |
18 | 18 | // i, I, a, A, o, O |
19 | 19 | // s |
20 | | -// ce, cb (without support for number of actions like c3e - TODO) |
| 20 | +// ce, cb |
21 | 21 | // cc |
22 | 22 | // S, C TODO |
23 | 23 | // cf<char>, cF<char>, ct<char>, cT<char> |
|
26 | 26 | // x, X |
27 | 27 | // J |
28 | 28 | // dd, D |
29 | | -// de, db (without support for number of actions like d3e - TODO) |
| 29 | +// de, db |
30 | 30 | // df<char>, dF<char>, dt<char>, dT<char> |
31 | 31 | // |
32 | 32 | // Yanking and pasting: |
|
48 | 48 | // |
49 | 49 |
|
50 | 50 | (function() { |
51 | | - var count = ""; |
52 | 51 | var sdir = "f"; |
53 | 52 | var buf = ""; |
54 | 53 | var yank = 0; |
55 | 54 | var mark = []; |
56 | | - var reptTimes = 0; |
| 55 | + var repeatCount = 0; |
| 56 | + function isLine(cm, line) { return line >= 0 && line < cm.lineCount(); } |
57 | 57 | function emptyBuffer() { buf = ""; } |
58 | 58 | function pushInBuffer(str) { buf += str; } |
59 | | - function pushCountDigit(digit) { return function(cm) {count += digit;}; } |
60 | | - function popCount() { var i = parseInt(count, 10); count = ""; return i || 1; } |
| 59 | + function pushRepeatCountDigit(digit) {return function(cm) {repeatCount = (repeatCount * 10) + digit}; } |
| 60 | + function getCountOrOne() { |
| 61 | + var i = repeatCount; |
| 62 | + return i || 1; |
| 63 | + } |
| 64 | + function clearCount() { |
| 65 | + repeatCount = 0; |
| 66 | + } |
61 | 67 | function iterTimes(func) { |
62 | | - for (var i = 0, c = popCount(); i < c; ++i) func(i, i == c - 1); |
| 68 | + for (var i = 0, c = getCountOrOne(); i < c; ++i) func(i, i == c - 1); |
| 69 | + clearCount(); |
63 | 70 | } |
64 | 71 | function countTimes(func) { |
65 | 72 | if (typeof func == "string") func = CodeMirror.commands[func]; |
|
93 | 100 | } |
94 | 101 |
|
95 | 102 | var word = [/\w/, /[^\w\s]/], bigWord = [/\S/]; |
96 | | - function findWord(line, pos, dir, regexps) { |
97 | | - var stop = 0, next = -1; |
98 | | - if (dir > 0) { stop = line.length; next = 0; } |
99 | | - var start = stop, end = stop; |
100 | | - // Find bounds of next one. |
101 | | - outer: for (; pos != stop; pos += dir) { |
102 | | - for (var i = 0; i < regexps.length; ++i) { |
103 | | - if (regexps[i].test(line.charAt(pos + next))) { |
104 | | - start = pos; |
105 | | - for (; pos != stop; pos += dir) { |
106 | | - if (!regexps[i].test(line.charAt(pos + next))) break; |
| 103 | + // Finds a word on the given line, and continue searching the next line if it can't find one. |
| 104 | + function findWord(cm, lineNum, pos, dir, regexps) { |
| 105 | + var line = cm.getLine(lineNum); |
| 106 | + while (true) { |
| 107 | + var stop = (dir > 0) ? line.length : -1; |
| 108 | + var wordStart = stop, wordEnd = stop; |
| 109 | + // Find bounds of next word. |
| 110 | + for (; pos != stop; pos += dir) { |
| 111 | + for (var i = 0; i < regexps.length; ++i) { |
| 112 | + if (regexps[i].test(line.charAt(pos))) { |
| 113 | + wordStart = pos; |
| 114 | + // Advance to end of word. |
| 115 | + for (; pos != stop && regexps[i].test(line.charAt(pos)); pos += dir) {} |
| 116 | + wordEnd = (dir > 0) ? pos : pos + 1; |
| 117 | + return { |
| 118 | + from: Math.min(wordStart, wordEnd), |
| 119 | + to: Math.max(wordStart, wordEnd), |
| 120 | + line: lineNum}; |
107 | 121 | } |
108 | | - end = pos; |
109 | | - break outer; |
110 | 122 | } |
111 | 123 | } |
| 124 | + // Advance to next/prev line. |
| 125 | + lineNum += dir; |
| 126 | + if (!isLine(cm, lineNum)) return null; |
| 127 | + line = cm.getLine(lineNum); |
| 128 | + pos = (dir > 0) ? 0 : line.length; |
112 | 129 | } |
113 | | - return {from: Math.min(start, end), to: Math.max(start, end)}; |
114 | 130 | } |
115 | | - function moveToWord(cm, regexps, dir, times, where) { |
| 131 | + /** |
| 132 | + * @param {boolean} cm CodeMirror object. |
| 133 | + * @param {regexp} regexps Regular expressions for word characters. |
| 134 | + * @param {number} dir Direction, +/- 1. |
| 135 | + * @param {number} times Number of times to advance word. |
| 136 | + * @param {string} where Go to "start" or "end" of word, 'e' vs 'w'. |
| 137 | + * @param {boolean} yank Whether we are finding words to yank. If true, |
| 138 | + * do not go to the next line to look for the last word. This is to |
| 139 | + * prevent deleting new line on 'dw' at the end of a line. |
| 140 | + */ |
| 141 | + function moveToWord(cm, regexps, dir, times, where, yank) { |
116 | 142 | var cur = cm.getCursor(); |
117 | | - |
| 143 | + if (yank) { |
| 144 | + where = 'start'; |
| 145 | + } |
118 | 146 | for (var i = 0; i < times; i++) { |
119 | | - var line = cm.getLine(cur.line), startCh = cur.ch, word; |
| 147 | + var startCh = cur.ch, startLine = cur.line, word; |
120 | 148 | while (true) { |
121 | | - // If we're at start/end of line, start on prev/next respectivly |
122 | | - if (cur.ch == line.length && dir > 0) { |
123 | | - cur.line++; |
124 | | - cur.ch = 0; |
125 | | - line = cm.getLine(cur.line); |
126 | | - } else if (cur.ch == 0 && dir < 0) { |
127 | | - cur.line--; |
128 | | - cur.ch = line.length; |
129 | | - line = cm.getLine(cur.line); |
| 149 | + // Search and advance. |
| 150 | + word = findWord(cm, cur.line, cur.ch, dir, regexps); |
| 151 | + if (word) { |
| 152 | + if (yank && times == 1 && dir == 1 && cur.line != word.line) { |
| 153 | + // Stop at end of line of last word. Don't want to delete line return |
| 154 | + // for dw if the last deleted word is at the end of a line. |
| 155 | + cur.ch = cm.getLine(cur.line).length; |
| 156 | + break; |
| 157 | + } else { |
| 158 | + // Move to the word we just found. If by moving to the word we end up |
| 159 | + // in the same spot, then move an extra character and search again. |
| 160 | + cur.line = word.line; |
| 161 | + if (dir > 0 && where == 'end') { |
| 162 | + // 'e' |
| 163 | + if (startCh != word.to - 1 || startLine != word.line) { |
| 164 | + cur.ch = word.to - 1; |
| 165 | + break; |
| 166 | + } else { |
| 167 | + cur.ch = word.to; |
| 168 | + } |
| 169 | + } else if (dir > 0 && where == 'start') { |
| 170 | + // 'w' |
| 171 | + if (startCh != word.from || startLine != word.line) { |
| 172 | + cur.ch = word.from; |
| 173 | + break; |
| 174 | + } else { |
| 175 | + cur.ch = word.to; |
| 176 | + } |
| 177 | + } else if (dir < 0 && where == 'end') { |
| 178 | + // 'ge' |
| 179 | + if (startCh != word.to || startLine != word.line) { |
| 180 | + cur.ch = word.to; |
| 181 | + break; |
| 182 | + } else { |
| 183 | + cur.ch = word.from - 1; |
| 184 | + } |
| 185 | + } else if (dir < 0 && where == 'start') { |
| 186 | + // 'b' |
| 187 | + if (startCh != word.from || startLine != word.line) { |
| 188 | + cur.ch = word.from; |
| 189 | + break; |
| 190 | + } else { |
| 191 | + cur.ch = word.from - 1; |
| 192 | + } |
| 193 | + } |
| 194 | + } |
| 195 | + } else { |
| 196 | + // No more words to be found. Move to end of document. |
| 197 | + for (; isLine(cm, cur.line + dir); cur.line += dir) {} |
| 198 | + cur.ch = (dir > 0) ? cm.getLine(cur.line).length : 0; |
| 199 | + break; |
130 | 200 | } |
131 | | - if (!line) break; |
132 | | - |
133 | | - // On to the actual searching |
134 | | - word = findWord(line, cur.ch, dir, regexps); |
135 | | - cur.ch = word[where == "end" ? "to" : "from"]; |
136 | | - if (startCh == cur.ch && word.from != word.to) cur.ch = word[dir < 0 ? "from" : "to"]; |
137 | | - else break; |
138 | 201 | } |
139 | 202 | } |
140 | 203 | return cur; |
|
220 | 283 |
|
221 | 284 | function enterInsertMode(cm) { |
222 | 285 | // enter insert mode: switch mode and cursor |
223 | | - popCount(); |
| 286 | + clearCount(); |
224 | 287 | cm.setOption("keyMap", "vim-insert"); |
225 | 288 | } |
226 | 289 |
|
|
238 | 301 | var map = CodeMirror.keyMap.vim = { |
239 | 302 | // Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces? |
240 | 303 | "'|'": function(cm) { |
241 | | - cm.setCursor(cm.getCursor().line, popCount() - 1, true); |
| 304 | + cm.setCursor(cm.getCursor().line, getCountOrOne() - 1, true); |
| 305 | + clearCount(); |
242 | 306 | }, |
243 | 307 | "A": function(cm) { |
244 | 308 | cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true); |
|
300 | 364 | if (fn) sdir != "r" ? CodeMirror.commands.findPrev(cm) : fn.findNext(cm); |
301 | 365 | }, |
302 | 366 | "Shift-G": function(cm) { |
303 | | - count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count, 10)-1); |
304 | | - popCount(); |
| 367 | + (repeatCount == 0) ? cm.setCursor(cm.lineCount()) : cm.setCursor(repeatCount - 1); |
| 368 | + clearCount(); |
305 | 369 | CodeMirror.commands.goLineStart(cm); |
306 | 370 | }, |
307 | 371 | "':'": function(cm) { |
|
325 | 389 | }; |
326 | 390 | }); |
327 | 391 |
|
328 | | - function addCountBindings(keyMap) { |
329 | | - // Add bindings for number keys |
330 | | - keyMap["0"] = function(cm) { |
331 | | - count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm); |
332 | | - }; |
333 | | - for (var i = 1; i < 10; ++i) keyMap[i] = pushCountDigit(i); |
334 | | - } |
335 | | - addCountBindings(CodeMirror.keyMap.vim); |
336 | | - |
337 | 392 | // main num keymap |
338 | 393 | // Add bindings that are influenced by number keys |
339 | 394 | iterObj({ |
|
401 | 456 | }); |
402 | 457 |
|
403 | 458 | CodeMirror.keyMap["vim-prefix-g"] = { |
404 | | - "E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "start"));}), |
405 | | - "Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "start"));}), |
406 | | - "G": function (cm) { cm.setCursor({line: 0, ch: cm.getCursor().ch});}, |
| 459 | + "E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "end"));}), |
| 460 | + "Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "end"));}), |
| 461 | + "G": function (cm) { |
| 462 | + cm.setCursor({line: repeatCount - 1, ch: cm.getCursor().ch}); |
| 463 | + clearCount(); |
| 464 | + }, |
407 | 465 | auto: "vim", nofallthrough: true, style: "fat-cursor" |
408 | 466 | }; |
409 | 467 |
|
|
428 | 486 | }, |
429 | 487 | nofallthrough: true, style: "fat-cursor" |
430 | 488 | }; |
431 | | - // FIXME - does not work for bindings like "d3e" |
432 | | - addCountBindings(CodeMirror.keyMap["vim-prefix-d"]); |
433 | 489 |
|
434 | 490 | CodeMirror.keyMap["vim-prefix-c"] = { |
435 | 491 | "B": function (cm) { |
|
593 | 649 | var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc']; |
594 | 650 |
|
595 | 651 | motions = { |
596 | | - 'B': function(cm, times) { return moveToWord(cm, word, -1, times); }, |
597 | | - 'Shift-B': function(cm, times) { return moveToWord(cm, bigWord, -1, times); }, |
598 | | - 'E': function(cm, times) { return moveToWord(cm, word, 1, times, 'end'); }, |
599 | | - 'Shift-E': function(cm, times) { return moveToWord(cm, bigWord, 1, times, 'end'); }, |
| 652 | + 'B': function(cm, times, yank) { return moveToWord(cm, word, -1, times, 'start', yank); }, |
| 653 | + 'Shift-B': function(cm, times, yank) { return moveToWord(cm, bigWord, -1, times, 'start', yank); }, |
| 654 | + 'E': function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'end', yank); }, |
| 655 | + 'Shift-E': function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'end', yank); }, |
600 | 656 | 'J': function(cm, times) { |
601 | 657 | var cur = cm.getCursor(); |
602 | 658 | return {line: cur.line+times, ch : cur.ch}; |
|
616 | 672 | var cur = cm.getCursor(); |
617 | 673 | return {line: cur.line, ch: cur.ch+times}; |
618 | 674 | }, |
619 | | - 'W': function(cm, times) { return moveToWord(cm, word, 1, times); }, |
620 | | - 'Shift-W': function(cm, times) { return moveToWord(cm, bigWord, 1, times); }, |
| 675 | + 'W': function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'start', yank); }, |
| 676 | + 'Shift-W': function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'start', yank); }, |
621 | 677 | "'^'": function(cm, times) { |
622 | 678 | var cur = cm.getCursor(); |
623 | 679 | var line = cm.getLine(cur.line).split(''); |
|
637 | 693 | "'%'": function(cm) { return findMatchedSymbol(cm, cm.getCursor()); }, |
638 | 694 | "Esc" : function(cm) { |
639 | 695 | cm.setOption('vim'); |
640 | | - reptTimes = 0; |
| 696 | + repeatCount = 0; |
641 | 697 |
|
642 | 698 | return cm.getCursor(); |
643 | 699 | } |
|
648 | 704 | CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) { |
649 | 705 | // Get our selected range |
650 | 706 | var start = cm.getCursor(); |
651 | | - var end = motions[key](cm, reptTimes ? reptTimes : 1); |
| 707 | + var end = motions[key](cm, repeatCount ? repeatCount : 1, true); |
652 | 708 |
|
653 | 709 | // Set swap var if range is of negative length |
654 | 710 | if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; |
|
658 | 714 | cm.replaceRange("", swap ? end : start, swap ? start : end); |
659 | 715 |
|
660 | 716 | // And clean up |
661 | | - reptTimes = 0; |
| 717 | + repeatCount = 0; |
662 | 718 | cm.setOption("keyMap", "vim"); |
663 | 719 | }; |
664 | 720 |
|
665 | 721 | CodeMirror.keyMap['vim-prefix-c'][key] = function(cm) { |
666 | 722 | var start = cm.getCursor(); |
667 | | - var end = motions[key](cm, reptTimes ? reptTimes : 1); |
| 723 | + var end = motions[key](cm, repeatCount ? repeatCount : 1, true); |
668 | 724 |
|
669 | 725 | if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; |
670 | 726 | pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); |
671 | 727 | cm.replaceRange("", swap ? end : start, swap ? start : end); |
672 | 728 |
|
673 | | - reptTimes = 0; |
| 729 | + repeatCount = 0; |
674 | 730 | cm.setOption('keyMap', 'vim-insert'); |
675 | 731 | }; |
676 | 732 |
|
677 | 733 | CodeMirror.keyMap['vim-prefix-y'][key] = function(cm) { |
678 | 734 | var start = cm.getCursor(); |
679 | | - var end = motions[key](cm, reptTimes ? reptTimes : 1); |
| 735 | + var end = motions[key](cm, repeatCount ? repeatCount : 1, true); |
680 | 736 |
|
681 | 737 | if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; |
682 | 738 | pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); |
683 | 739 |
|
684 | | - reptTimes = 0; |
| 740 | + repeatCount = 0; |
685 | 741 | cm.setOption("keyMap", "vim"); |
686 | 742 | }; |
687 | 743 |
|
688 | 744 | CodeMirror.keyMap['vim'][key] = function(cm) { |
689 | | - var cur = motions[key](cm, reptTimes ? reptTimes : 1); |
| 745 | + var cur = motions[key](cm, repeatCount ? repeatCount : 1); |
690 | 746 | cm.setCursor(cur.line, cur.ch); |
691 | 747 |
|
692 | | - reptTimes = 0; |
| 748 | + repeatCount = 0; |
693 | 749 | }; |
694 | 750 | }); |
695 | 751 |
|
696 | | - var nums = [1,2,3,4,5,6,7,8,9]; |
697 | | - iterList(nums, function(key, index, array) { |
698 | | - CodeMirror.keyMap['vim'][key] = function (cm) { |
699 | | - reptTimes = (reptTimes * 10) + key; |
700 | | - }; |
701 | | - CodeMirror.keyMap['vim-prefix-d'][key] = function (cm) { |
702 | | - reptTimes = (reptTimes * 10) + key; |
703 | | - }; |
704 | | - CodeMirror.keyMap['vim-prefix-y'][key] = function (cm) { |
705 | | - reptTimes = (reptTimes * 10) + key; |
706 | | - }; |
707 | | - CodeMirror.keyMap['vim-prefix-c'][key] = function (cm) { |
708 | | - reptTimes = (reptTimes * 10) + key; |
| 752 | + function addCountBindings(keyMapName) { |
| 753 | + // Add bindings for number keys |
| 754 | + keyMap = CodeMirror.keyMap[keyMapName]; |
| 755 | + keyMap["0"] = function(cm) { |
| 756 | + if (repeatCount > 0) { |
| 757 | + pushRepeatCountDigit(0)(cm); |
| 758 | + } else { |
| 759 | + CodeMirror.commands.goLineStart(cm); |
| 760 | + } |
709 | 761 | }; |
710 | | - }); |
| 762 | + for (var i = 1; i < 10; ++i) { |
| 763 | + keyMap[i] = pushRepeatCountDigit(i); |
| 764 | + } |
| 765 | + } |
| 766 | + addCountBindings('vim'); |
| 767 | + addCountBindings('vim-prefix-d'); |
| 768 | + addCountBindings('vim-prefix-y'); |
| 769 | + addCountBindings('vim-prefix-c'); |
711 | 770 |
|
712 | 771 | // Create our keymaps for each operator and make xa and xi where x is an operator |
713 | 772 | // change to the corrosponding keymap |
|
721 | 780 | }; |
722 | 781 |
|
723 | 782 | CodeMirror.keyMap['vim-prefix-'+key]['A'] = function(cm) { |
724 | | - reptTimes = 0; |
| 783 | + repeatCount = 0; |
725 | 784 | cm.setOption('keyMap', 'vim-prefix-' + key + 'a'); |
726 | 785 | }; |
727 | 786 |
|
728 | 787 | CodeMirror.keyMap['vim-prefix-'+key]['I'] = function(cm) { |
729 | | - reptTimes = 0; |
| 788 | + repeatCount = 0; |
730 | 789 | cm.setOption('keyMap', 'vim-prefix-' + key + 'i'); |
731 | 790 | }; |
732 | 791 | }); |
|
0 commit comments