Skip to content

Commit e4acffd

Browse files
msuozzomarijnh
authored andcommitted
[vim] Add an interface to clear user-defined mappings.
In Vim, this is the 'mapclear' command so that's what I've elected to name the function in the API. I've also updated the Vim test bindings to clear bindings after each test that adds them. Notably, this testing change surfaced an off-by-one error in the noremap feature added recently. A fix for this is included.
1 parent 46ae703 commit e4acffd

2 files changed

Lines changed: 78 additions & 12 deletions

File tree

keymap/vim.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@
753753
var ctxsToMap = toCtxArray(ctx);
754754
// Look through all actual defaults to find a map candidate.
755755
var actualLength = defaultKeymap.length, origLength = defaultKeymapLength;
756-
for (var i = actualLength - origLength - 1;
756+
for (var i = actualLength - origLength;
757757
i < actualLength && ctxsToMap.length;
758758
i++) {
759759
var mapping = defaultKeymap[i];
@@ -781,6 +781,40 @@
781781
}
782782
// TODO: Create non-recursive keyToKey mappings for the unmapped contexts once those exist.
783783
},
784+
// Remove all user-defined mappings for the provided context.
785+
mapclear: function(ctx) {
786+
// Partition the existing keymap into user-defined and true defaults.
787+
var actualLength = defaultKeymap.length,
788+
origLength = defaultKeymapLength;
789+
var userKeymap = defaultKeymap.slice(0, actualLength - origLength);
790+
defaultKeymap = defaultKeymap.slice(actualLength - origLength);
791+
if (ctx) {
792+
// If a specific context is being cleared, we need to keep mappings
793+
// from all other contexts.
794+
for (var i = userKeymap.length - 1; i >= 0; i--) {
795+
var mapping = userKeymap[i];
796+
if (ctx !== mapping.context) {
797+
if (mapping.context) {
798+
this._mapCommand(mapping);
799+
} else {
800+
// `mapping` applies to all contexts so create keymap copies
801+
// for each context except the one being cleared.
802+
var contexts = ['normal', 'insert', 'visual'];
803+
for (var j in contexts) {
804+
if (contexts[j] !== ctx) {
805+
var newMapping = {};
806+
for (var key in mapping) {
807+
newMapping[key] = mapping[key];
808+
}
809+
newMapping.context = contexts[j];
810+
this._mapCommand(newMapping);
811+
}
812+
}
813+
}
814+
}
815+
}
816+
}
817+
},
784818
// TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace
785819
// them, or somehow make them work with the existing CodeMirror setOption/getOption API.
786820
setOption: setOption,

test/vim_test.js

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4140,17 +4140,45 @@ testVim('ex_set_filetype_null', function(cm, vim, helpers) {
41404140
helpers.doEx('set filetype=');
41414141
eq('null', cm.getMode().name);
41424142
});
4143-
// TODO: Reset key maps after each test.
4143+
4144+
testVim('mapclear', function(cm, vim, helpers) {
4145+
CodeMirror.Vim.map('w', 'l');
4146+
cm.setCursor(0, 0);
4147+
helpers.assertCursorAt(0, 0);
4148+
helpers.doKeys('w');
4149+
helpers.assertCursorAt(0, 1);
4150+
CodeMirror.Vim.mapclear('visual');
4151+
helpers.doKeys('v', 'w', 'v');
4152+
helpers.assertCursorAt(0, 4);
4153+
helpers.doKeys('w');
4154+
helpers.assertCursorAt(0, 5);
4155+
CodeMirror.Vim.mapclear();
4156+
}, { value: 'abc abc' });
4157+
testVim('mapclear_context', function(cm, vim, helpers) {
4158+
CodeMirror.Vim.map('w', 'l', 'normal');
4159+
cm.setCursor(0, 0);
4160+
helpers.assertCursorAt(0, 0);
4161+
helpers.doKeys('w');
4162+
helpers.assertCursorAt(0, 1);
4163+
CodeMirror.Vim.mapclear('normal');
4164+
helpers.doKeys('w');
4165+
helpers.assertCursorAt(0, 4);
4166+
CodeMirror.Vim.mapclear();
4167+
}, { value: 'abc abc' });
4168+
41444169
testVim('ex_map_key2key', function(cm, vim, helpers) {
41454170
helpers.doEx('map a x');
41464171
helpers.doKeys('a');
41474172
helpers.assertCursorAt(0, 0);
41484173
eq('bc', cm.getValue());
4174+
CodeMirror.Vim.mapclear();
41494175
}, { value: 'abc' });
41504176
testVim('ex_unmap_key2key', function(cm, vim, helpers) {
4177+
helpers.doEx('map a x');
41514178
helpers.doEx('unmap a');
41524179
helpers.doKeys('a');
41534180
eq('vim-insert', cm.getOption('keyMap'));
4181+
CodeMirror.Vim.mapclear();
41544182
}, { value: 'abc' });
41554183
testVim('ex_unmap_key2key_does_not_remove_default', function(cm, vim, helpers) {
41564184
try {
@@ -4159,6 +4187,7 @@ testVim('ex_unmap_key2key_does_not_remove_default', function(cm, vim, helpers) {
41594187
} catch (expected) {}
41604188
helpers.doKeys('a');
41614189
eq('vim-insert', cm.getOption('keyMap'));
4190+
CodeMirror.Vim.mapclear();
41624191
}, { value: 'abc' });
41634192
testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) {
41644193
helpers.doEx('map ; :');
@@ -4168,12 +4197,14 @@ testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) {
41684197
}
41694198
helpers.doKeys(';');
41704199
eq(dialogOpened, true);
4200+
CodeMirror.Vim.mapclear();
41714201
});
41724202
testVim('ex_map_ex2key:', function(cm, vim, helpers) {
41734203
helpers.doEx('map :del x');
41744204
helpers.doEx('del');
41754205
helpers.assertCursorAt(0, 0);
41764206
eq('bc', cm.getValue());
4207+
CodeMirror.Vim.mapclear();
41774208
}, { value: 'abc' });
41784209
testVim('ex_map_ex2ex', function(cm, vim, helpers) {
41794210
helpers.doEx('map :del :w');
@@ -4188,6 +4219,7 @@ testVim('ex_map_ex2ex', function(cm, vim, helpers) {
41884219
CodeMirror.commands.save = tmp;
41894220
eq(written, true);
41904221
eq(actualCm, cm);
4222+
CodeMirror.Vim.mapclear();
41914223
});
41924224
testVim('ex_map_key2ex', function(cm, vim, helpers) {
41934225
helpers.doEx('map a :w');
@@ -4202,6 +4234,7 @@ testVim('ex_map_key2ex', function(cm, vim, helpers) {
42024234
CodeMirror.commands.save = tmp;
42034235
eq(written, true);
42044236
eq(actualCm, cm);
4237+
CodeMirror.Vim.mapclear();
42054238
});
42064239
testVim('ex_map_key2key_visual_api', function(cm, vim, helpers) {
42074240
CodeMirror.Vim.map('b', ':w', 'visual');
@@ -4221,6 +4254,7 @@ testVim('ex_map_key2key_visual_api', function(cm, vim, helpers) {
42214254
eq(actualCm, cm);
42224255

42234256
CodeMirror.commands.save = tmp;
4257+
CodeMirror.Vim.mapclear();
42244258
});
42254259
testVim('ex_imap', function(cm, vim, helpers) {
42264260
CodeMirror.Vim.map('jk', '<Esc>', 'insert');
@@ -4240,12 +4274,14 @@ testVim('ex_imap', function(cm, vim, helpers) {
42404274
cm.setCursor(0, 0);
42414275
helpers.doKeys('.');
42424276
eq('foo4\nfoo8\nfoodefg', cm.getValue());
4277+
CodeMirror.Vim.mapclear();
42434278
}, { value: '1234\n5678\nabcdefg' });
42444279
testVim('ex_unmap_api', function(cm, vim, helpers) {
42454280
CodeMirror.Vim.map('<Alt-X>', 'gg', 'normal');
42464281
is(CodeMirror.Vim.handleKey(cm, "<Alt-X>", "normal"), "Alt-X key is mapped");
42474282
CodeMirror.Vim.unmap("<Alt-X>", "normal");
42484283
is(!CodeMirror.Vim.handleKey(cm, "<Alt-X>", "normal"), "Alt-X key is unmapped");
4284+
CodeMirror.Vim.mapclear();
42494285
});
42504286
// Testing registration of functions as ex-commands and mapping to <Key>-keys
42514287
testVim('ex_api_test', function(cm, vim, helpers) {
@@ -4260,13 +4296,15 @@ testVim('ex_api_test', function(cm, vim, helpers) {
42604296
CodeMirror.Vim.map('<C-CR><Space>',':ext');
42614297
helpers.doKeys('<C-CR>','<Space>');
42624298
is(res,'Mapping to key failed');
4299+
CodeMirror.Vim.mapclear();
42634300
});
42644301
// For now, this test needs to be last because it messes up : for future tests.
42654302
testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) {
42664303
helpers.doEx('map : x');
42674304
helpers.doKeys(':');
42684305
helpers.assertCursorAt(0, 0);
42694306
eq('bc', cm.getValue());
4307+
CodeMirror.Vim.mapclear();
42704308
}, { value: 'abc' });
42714309

42724310
testVim('noremap', function(cm, vim, helpers) {
@@ -4281,7 +4319,7 @@ testVim('noremap', function(cm, vim, helpers) {
42814319
helpers.doKeys('i', ';', '<Esc>');
42824320
eq('w;1rd1', cm.getValue());
42834321
// unmap all mappings
4284-
CodeMirror.Vim.unmap(';');
4322+
CodeMirror.Vim.mapclear();
42854323
}, { value: 'wOrd1' });
42864324
testVim('noremap_swap', function(cm, vim, helpers) {
42874325
CodeMirror.Vim.noremap('i', 'a', 'normal');
@@ -4294,8 +4332,7 @@ testVim('noremap_swap', function(cm, vim, helpers) {
42944332
helpers.doKeys('<Esc>', 'i');
42954333
eqCursorPos(Pos(0, 1), cm.getCursor());
42964334
// unmap all mappings
4297-
CodeMirror.Vim.unmap('a', 'normal');
4298-
CodeMirror.Vim.unmap('i', 'normal');
4335+
CodeMirror.Vim.mapclear();
42994336
}, { value: 'foo' });
43004337
testVim('noremap_map_interaction', function(cm, vim, helpers) {
43014338
// noremap should clobber map
@@ -4312,10 +4349,7 @@ testVim('noremap_map_interaction', function(cm, vim, helpers) {
43124349
helpers.doKeys('m');
43134350
eqCursorPos(Pos(1, 2), cm.getCursor());
43144351
// unmap all mappings
4315-
CodeMirror.Vim.unmap('m');
4316-
CodeMirror.Vim.unmap('l');
4317-
CodeMirror.Vim.unmap(';');
4318-
CodeMirror.Vim.unmap(';');
4352+
CodeMirror.Vim.mapclear();
43194353
}, { value: 'wOrd1\nwOrd2' });
43204354
testVim('noremap_map_interaction2', function(cm, vim, helpers) {
43214355
// map should point to the most recent noremap
@@ -4328,9 +4362,7 @@ testVim('noremap_map_interaction2', function(cm, vim, helpers) {
43284362
helpers.doKeys('m');
43294363
eqCursorPos(Pos(0, 0), cm.getCursor());
43304364
// unmap all mappings
4331-
CodeMirror.Vim.unmap(';');
4332-
CodeMirror.Vim.unmap('m');
4333-
CodeMirror.Vim.unmap(';');
4365+
CodeMirror.Vim.mapclear();
43344366
}, { value: 'wOrd1\nwOrd2' });
43354367

43364368
// Test event handlers

0 commit comments

Comments
 (0)