Skip to content

Commit 6d04226

Browse files
committed
[vim] Support text objects in visual mode
1 parent 5432702 commit 6d04226

File tree

2 files changed

+73
-14
lines changed

2 files changed

+73
-14
lines changed

keymap/vim.js

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,13 @@
172172
{ keys: '<C-o>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }},
173173
{ keys: '<C-e>', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }},
174174
{ keys: '<C-y>', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }},
175-
{ keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }},
176-
{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }},
175+
{ keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' },
176+
{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
177177
{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
178-
{ keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }},
178+
{ keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
179179
{ keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank' }},
180-
{ keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }},
181-
{ keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }},
180+
{ keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
181+
{ keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
182182
{ keys: 'v', type: 'action', action: 'toggleVisualMode' },
183183
{ keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }},
184184
{ keys: '<C-v>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
@@ -191,7 +191,7 @@
191191
{ keys: 'q<character>', type: 'action', action: 'enterMacroRecordMode' },
192192
// Handle Replace-mode as a special case of insert mode.
193193
{ keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }},
194-
{ keys: 'u', type: 'action', action: 'undo' },
194+
{ keys: 'u', type: 'action', action: 'undo', context: 'normal' },
195195
{ keys: 'u', type: 'action', action: 'changeCase', actionArgs: {toLower: true}, context: 'visual', isEdit: true },
196196
{ keys: 'U',type: 'action', action: 'changeCase', actionArgs: {toLower: false}, context: 'visual', isEdit: true },
197197
{ keys: '<C-r>', type: 'action', action: 'redo' },
@@ -945,7 +945,7 @@
945945
var bestMatch;
946946
for (var i = 0; i < matches.full.length; i++) {
947947
var match = matches.full[i];
948-
if (!bestMatch || match.context == context) {
948+
if (!bestMatch) {
949949
bestMatch = match;
950950
}
951951
}
@@ -1507,8 +1507,8 @@
15071507

15081508
var equal = cursorEqual(cursor, best);
15091509
var between = (motionArgs.forward) ?
1510-
cusrorIsBetween(cursor, mark, best) :
1511-
cusrorIsBetween(best, mark, cursor);
1510+
cursorIsBetween(cursor, mark, best) :
1511+
cursorIsBetween(best, mark, cursor);
15121512

15131513
if (equal || between) {
15141514
best = mark;
@@ -1760,7 +1760,11 @@
17601760
return null;
17611761
}
17621762

1763-
return [tmp.start, tmp.end];
1763+
if (!cm.state.vim.visualMode) {
1764+
return [tmp.start, tmp.end];
1765+
} else {
1766+
return expandSelection(cm, tmp.start, tmp.end);
1767+
}
17641768
},
17651769

17661770
repeatLastCharacterSearch: function(cm, motionArgs) {
@@ -2634,7 +2638,13 @@
26342638
}
26352639
return false;
26362640
}
2637-
function cusrorIsBetween(cur1, cur2, cur3) {
2641+
function cursorMin(cur1, cur2) {
2642+
return cursorIsBefore(cur1, cur2) ? cur1 : cur2;
2643+
}
2644+
function cursorMax(cur1, cur2) {
2645+
return cursorIsBefore(cur1, cur2) ? cur2 : cur1;
2646+
}
2647+
function cursorIsBetween(cur1, cur2, cur3) {
26382648
// returns true if cur2 is between cur1 and cur3.
26392649
var cur1before2 = cursorIsBefore(cur1, cur2);
26402650
var cur2before3 = cursorIsBefore(cur2, cur3);
@@ -2861,6 +2871,33 @@
28612871
'visualLine': vim.visualLine,
28622872
'visualBlock': block};
28632873
}
2874+
function expandSelection(cm, start, end) {
2875+
var head = cm.getCursor('head');
2876+
var anchor = cm.getCursor('anchor');
2877+
var tmp;
2878+
if (cursorIsBefore(end, start)) {
2879+
tmp = end;
2880+
end = start;
2881+
start = tmp;
2882+
}
2883+
if (cursorIsBefore(head, anchor)) {
2884+
head = cursorMin(start, head);
2885+
anchor = cursorMax(anchor, end);
2886+
} else {
2887+
anchor = cursorMin(start, anchor);
2888+
head = cursorMax(head, end);
2889+
}
2890+
return [anchor, head];
2891+
}
2892+
function getHead(cm) {
2893+
var cur = cm.getCursor('head');
2894+
if (cm.getSelection().length == 1) {
2895+
// Small corner case when only 1 character is selected. The "real"
2896+
// head is the left of head and anchor.
2897+
cur = cursorMin(cur, cm.getCursor('anchor'));
2898+
}
2899+
return cur;
2900+
}
28642901

28652902
function exitVisualMode(cm) {
28662903
cm.off('mousedown', exitVisualMode);
@@ -2933,7 +2970,7 @@
29332970
}
29342971

29352972
function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
2936-
var cur = cm.getCursor();
2973+
var cur = getHead(cm);
29372974
var line = cm.getLine(cur.line);
29382975
var idx = cur.ch;
29392976

@@ -3319,7 +3356,7 @@
33193356
// TODO: perhaps this finagling of start and end positions belonds
33203357
// in codmirror/replaceRange?
33213358
function selectCompanionObject(cm, symb, inclusive) {
3322-
var cur = cm.getCursor(), start, end;
3359+
var cur = getHead(cm), start, end;
33233360

33243361
var bracketRegexp = ({
33253362
'(': /[()]/, ')': /[()]/,
@@ -3364,7 +3401,7 @@
33643401
// have identical opening and closing symbols
33653402
// TODO support across multiple lines
33663403
function findBeginningAndEnd(cm, symb, inclusive) {
3367-
var cur = copyCursor(cm.getCursor());
3404+
var cur = copyCursor(getHead(cm));
33683405
var line = cm.getLine(cur.line);
33693406
var chars = line.split('');
33703407
var start, end, i, len;

test/vim_test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,28 @@ testEdit('di]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di]', 'a\t[]b');
11251125
testEdit('da[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da[', 'a\tb');
11261126
testEdit('da]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da]', 'a\tb');
11271127

1128+
function testSelection(name, before, pos, keys, sel) {
1129+
return testVim(name, function(cm, vim, helpers) {
1130+
var ch = before.search(pos)
1131+
var line = before.substring(0, ch).split('\n').length - 1;
1132+
if (line) {
1133+
ch = before.substring(0, ch).split('\n').pop().length;
1134+
}
1135+
cm.setCursor(line, ch);
1136+
helpers.doKeys.apply(this, keys.split(''));
1137+
eq(sel, cm.getSelection());
1138+
}, {value: before});
1139+
}
1140+
testSelection('viw_middle_spc', 'foo \tbAr\t baz', /A/, 'viw', 'bAr');
1141+
testSelection('vaw_middle_spc', 'foo \tbAr\t baz', /A/, 'vaw', 'bAr\t ');
1142+
testSelection('viw_middle_punct', 'foo \tbAr,\t baz', /A/, 'viw', 'bAr');
1143+
testSelection('vaW_middle_punct', 'foo \tbAr,\t baz', /A/, 'vaW', 'bAr,\t ');
1144+
testSelection('viw_start_spc', 'foo \tbAr\t baz', /b/, 'viw', 'bAr');
1145+
testSelection('viw_end_spc', 'foo \tbAr\t baz', /r/, 'viw', 'bAr');
1146+
testSelection('viw_eol', 'foo \tbAr', /r/, 'viw', 'bAr');
1147+
testSelection('vi{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'vi{', '\n\tbar\n\t');
1148+
testSelection('va{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'va{', '{\n\tbar\n\t}');
1149+
11281150
// Operator-motion tests
11291151
testVim('D', function(cm, vim, helpers) {
11301152
cm.setCursor(0, 3);

0 commit comments

Comments
 (0)