Skip to content

Commit e4d0c61

Browse files
mightyguavamarijnh
authored andcommitted
[vim keymap] Lots of fixes for navigation.
Go to line with Shift+G works again. Updated navigation behavior to match closer to vim. dw/de at end of line will no longer delete newline.
1 parent 9725882 commit e4d0c61

File tree

1 file changed

+148
-89
lines changed

1 file changed

+148
-89
lines changed

keymap/vim.js

Lines changed: 148 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// Entering insert mode:
1818
// i, I, a, A, o, O
1919
// s
20-
// ce, cb (without support for number of actions like c3e - TODO)
20+
// ce, cb
2121
// cc
2222
// S, C TODO
2323
// cf<char>, cF<char>, ct<char>, cT<char>
@@ -26,7 +26,7 @@
2626
// x, X
2727
// J
2828
// dd, D
29-
// de, db (without support for number of actions like d3e - TODO)
29+
// de, db
3030
// df<char>, dF<char>, dt<char>, dT<char>
3131
//
3232
// Yanking and pasting:
@@ -48,18 +48,25 @@
4848
//
4949

5050
(function() {
51-
var count = "";
5251
var sdir = "f";
5352
var buf = "";
5453
var yank = 0;
5554
var mark = [];
56-
var reptTimes = 0;
55+
var repeatCount = 0;
56+
function isLine(cm, line) { return line >= 0 && line < cm.lineCount(); }
5757
function emptyBuffer() { buf = ""; }
5858
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+
}
6167
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();
6370
}
6471
function countTimes(func) {
6572
if (typeof func == "string") func = CodeMirror.commands[func];
@@ -93,48 +100,104 @@
93100
}
94101

95102
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};
107121
}
108-
end = pos;
109-
break outer;
110122
}
111123
}
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;
112129
}
113-
return {from: Math.min(start, end), to: Math.max(start, end)};
114130
}
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) {
116142
var cur = cm.getCursor();
117-
143+
if (yank) {
144+
where = 'start';
145+
}
118146
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;
120148
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;
130200
}
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;
138201
}
139202
}
140203
return cur;
@@ -220,7 +283,7 @@
220283

221284
function enterInsertMode(cm) {
222285
// enter insert mode: switch mode and cursor
223-
popCount();
286+
clearCount();
224287
cm.setOption("keyMap", "vim-insert");
225288
}
226289

@@ -238,7 +301,8 @@
238301
var map = CodeMirror.keyMap.vim = {
239302
// Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces?
240303
"'|'": function(cm) {
241-
cm.setCursor(cm.getCursor().line, popCount() - 1, true);
304+
cm.setCursor(cm.getCursor().line, getCountOrOne() - 1, true);
305+
clearCount();
242306
},
243307
"A": function(cm) {
244308
cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true);
@@ -300,8 +364,8 @@
300364
if (fn) sdir != "r" ? CodeMirror.commands.findPrev(cm) : fn.findNext(cm);
301365
},
302366
"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();
305369
CodeMirror.commands.goLineStart(cm);
306370
},
307371
"':'": function(cm) {
@@ -325,15 +389,6 @@
325389
};
326390
});
327391

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-
337392
// main num keymap
338393
// Add bindings that are influenced by number keys
339394
iterObj({
@@ -401,9 +456,12 @@
401456
});
402457

403458
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+
},
407465
auto: "vim", nofallthrough: true, style: "fat-cursor"
408466
};
409467

@@ -428,8 +486,6 @@
428486
},
429487
nofallthrough: true, style: "fat-cursor"
430488
};
431-
// FIXME - does not work for bindings like "d3e"
432-
addCountBindings(CodeMirror.keyMap["vim-prefix-d"]);
433489

434490
CodeMirror.keyMap["vim-prefix-c"] = {
435491
"B": function (cm) {
@@ -593,10 +649,10 @@
593649
var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc'];
594650

595651
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); },
600656
'J': function(cm, times) {
601657
var cur = cm.getCursor();
602658
return {line: cur.line+times, ch : cur.ch};
@@ -616,8 +672,8 @@
616672
var cur = cm.getCursor();
617673
return {line: cur.line, ch: cur.ch+times};
618674
},
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); },
621677
"'^'": function(cm, times) {
622678
var cur = cm.getCursor();
623679
var line = cm.getLine(cur.line).split('');
@@ -637,7 +693,7 @@
637693
"'%'": function(cm) { return findMatchedSymbol(cm, cm.getCursor()); },
638694
"Esc" : function(cm) {
639695
cm.setOption('vim');
640-
reptTimes = 0;
696+
repeatCount = 0;
641697

642698
return cm.getCursor();
643699
}
@@ -648,7 +704,7 @@
648704
CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) {
649705
// Get our selected range
650706
var start = cm.getCursor();
651-
var end = motions[key](cm, reptTimes ? reptTimes : 1);
707+
var end = motions[key](cm, repeatCount ? repeatCount : 1, true);
652708

653709
// Set swap var if range is of negative length
654710
if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
@@ -658,56 +714,59 @@
658714
cm.replaceRange("", swap ? end : start, swap ? start : end);
659715

660716
// And clean up
661-
reptTimes = 0;
717+
repeatCount = 0;
662718
cm.setOption("keyMap", "vim");
663719
};
664720

665721
CodeMirror.keyMap['vim-prefix-c'][key] = function(cm) {
666722
var start = cm.getCursor();
667-
var end = motions[key](cm, reptTimes ? reptTimes : 1);
723+
var end = motions[key](cm, repeatCount ? repeatCount : 1, true);
668724

669725
if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
670726
pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
671727
cm.replaceRange("", swap ? end : start, swap ? start : end);
672728

673-
reptTimes = 0;
729+
repeatCount = 0;
674730
cm.setOption('keyMap', 'vim-insert');
675731
};
676732

677733
CodeMirror.keyMap['vim-prefix-y'][key] = function(cm) {
678734
var start = cm.getCursor();
679-
var end = motions[key](cm, reptTimes ? reptTimes : 1);
735+
var end = motions[key](cm, repeatCount ? repeatCount : 1, true);
680736

681737
if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
682738
pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
683739

684-
reptTimes = 0;
740+
repeatCount = 0;
685741
cm.setOption("keyMap", "vim");
686742
};
687743

688744
CodeMirror.keyMap['vim'][key] = function(cm) {
689-
var cur = motions[key](cm, reptTimes ? reptTimes : 1);
745+
var cur = motions[key](cm, repeatCount ? repeatCount : 1);
690746
cm.setCursor(cur.line, cur.ch);
691747

692-
reptTimes = 0;
748+
repeatCount = 0;
693749
};
694750
});
695751

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+
}
709761
};
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');
711770

712771
// Create our keymaps for each operator and make xa and xi where x is an operator
713772
// change to the corrosponding keymap
@@ -721,12 +780,12 @@
721780
};
722781

723782
CodeMirror.keyMap['vim-prefix-'+key]['A'] = function(cm) {
724-
reptTimes = 0;
783+
repeatCount = 0;
725784
cm.setOption('keyMap', 'vim-prefix-' + key + 'a');
726785
};
727786

728787
CodeMirror.keyMap['vim-prefix-'+key]['I'] = function(cm) {
729-
reptTimes = 0;
788+
repeatCount = 0;
730789
cm.setOption('keyMap', 'vim-prefix-' + key + 'i');
731790
};
732791
});

0 commit comments

Comments
 (0)