Skip to content

Commit b6800d1

Browse files
msuozzomarijnh
authored andcommitted
[vim] Add a non-recursive map command
This will allow for more powerful mapping capabilities like arbitrary command remapping, rebinding the home row, etc. This doesn't quite match Vim's non-recursive behavior because there's no support for non-recursive keyToKey mappings. Without this support, we'd just be creating normal, recursive keyToKey mappings which I think would be even more frustrating and/or surprising (e.g. two inverse non-recursive maps would cause infinite recursion). Once this support is added for this, though, it will be trivial to create these mappings at the end of the added function and achieve the most accurate behavior. One potential paper cut with this implementation is that multiple calls to `unmap` may be required to fully remove the `noremap`ed values. I think this isn't that severe though because this looks to be the current behavior with all other `map` operations.
1 parent 49490e5 commit b6800d1

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

keymap/vim.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
/**
55
* Supported keybindings:
6-
* Too many to list. Refer to defaultKeyMap below.
6+
* Too many to list. Refer to defaultKeymap below.
77
*
88
* Supported Ex commands:
99
* Refer to defaultExCommandMap below.
@@ -207,6 +207,7 @@
207207
// Ex command
208208
{ keys: ':', type: 'ex' }
209209
];
210+
var defaultKeymapLength = defaultKeymap.length;
210211

211212
/**
212213
* Ex commands
@@ -742,6 +743,44 @@
742743
unmap: function(lhs, ctx) {
743744
exCommandDispatcher.unmap(lhs, ctx);
744745
},
746+
// Non-recursive map function.
747+
// NOTE: This will not create mappings to key maps that aren't present
748+
// in the default key map. See TODO at bottom of function.
749+
noremap: function(lhs, rhs, ctx) {
750+
function toCtxArray(ctx) {
751+
return ctx ? [ctx] : ['normal', 'insert', 'visual'];
752+
}
753+
var ctxsToMap = toCtxArray(ctx);
754+
// Look through all actual defaults to find a map candidate.
755+
var actualLength = defaultKeymap.length, origLength = defaultKeymapLength;
756+
for (var i = actualLength - origLength - 1;
757+
i < actualLength && ctxsToMap.length;
758+
i++) {
759+
var mapping = defaultKeymap[i];
760+
// Omit mappings that operate in the wrong context(s) and those of invalid type.
761+
if (mapping.keys == rhs &&
762+
(!ctx || !mapping.context || mapping.context === ctx) &&
763+
mapping.type.substr(0, 2) !== 'ex' &&
764+
mapping.type.substr(0, 3) !== 'key') {
765+
// Make a shallow copy of the original keymap entry.
766+
var newMapping = {};
767+
for (var key in mapping) {
768+
newMapping[key] = mapping[key];
769+
}
770+
// Modify it point to the new mapping with the proper context.
771+
newMapping.keys = lhs;
772+
if (ctx && !newMapping.context) {
773+
newMapping.context = ctx;
774+
}
775+
// Add it to the keymap with a higher priority than the original.
776+
this._mapCommand(newMapping);
777+
// Record the mapped contexts as complete.
778+
var mappedCtxs = toCtxArray(mapping.context);
779+
ctxsToMap = ctxsToMap.filter(function(el) { return mappedCtxs.indexOf(el) === -1; });
780+
}
781+
}
782+
// TODO: Create non-recursive keyToKey mappings for the unmapped contexts once those exist.
783+
},
745784
// TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace
746785
// them, or somehow make them work with the existing CodeMirror setOption/getOption API.
747786
setOption: setOption,

test/vim_test.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4245,7 +4245,6 @@ testVim('ex_unmap_api', function(cm, vim, helpers) {
42454245
CodeMirror.Vim.unmap("<Alt-X>", "normal");
42464246
is(!CodeMirror.Vim.handleKey(cm, "<Alt-X>", "normal"), "Alt-X key is unmapped");
42474247
});
4248-
42494248
// Testing registration of functions as ex-commands and mapping to <Key>-keys
42504249
testVim('ex_api_test', function(cm, vim, helpers) {
42514250
var res=false;
@@ -4268,6 +4267,30 @@ testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) {
42684267
eq('bc', cm.getValue());
42694268
}, { value: 'abc' });
42704269

4270+
testVim('noremap', function(cm, vim, helpers) {
4271+
CodeMirror.Vim.noremap(';', 'l');
4272+
cm.setCursor(0, 0);
4273+
eq('wOrd1', cm.getValue());
4274+
// Mapping should work in normal mode.
4275+
helpers.doKeys(';', 'r', '1');
4276+
eq('w1rd1', cm.getValue());
4277+
// Mapping will not work in insert mode because of no current fallback
4278+
// keyToKey mapping support.
4279+
helpers.doKeys('i', ';', '<Esc>');
4280+
eq('w;1rd1', cm.getValue());
4281+
}, { value: 'wOrd1' });
4282+
testVim('noremap_swap', function(cm, vim, helpers) {
4283+
CodeMirror.Vim.noremap('i', 'a', 'normal');
4284+
CodeMirror.Vim.noremap('a', 'i', 'normal');
4285+
cm.setCursor(0, 0);
4286+
// 'a' should act like 'i'.
4287+
helpers.doKeys('a');
4288+
eqCursorPos(Pos(0, 0), cm.getCursor());
4289+
// ...and 'i' should act like 'a'.
4290+
helpers.doKeys('<Esc>', 'i');
4291+
eqCursorPos(Pos(0, 1), cm.getCursor());
4292+
}, { value: 'foo' });
4293+
42714294
// Test event handlers
42724295
testVim('beforeSelectionChange', function(cm, vim, helpers) {
42734296
cm.setCursor(0, 100);

0 commit comments

Comments
 (0)