Skip to content

Commit 59c44ef

Browse files
authored
support both <A-*> and unicode mapping on mac (#194)
1 parent b0bfa43 commit 59c44ef

File tree

3 files changed

+84
-17
lines changed

3 files changed

+84
-17
lines changed

src/types.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ type allCommands = {
192192
interlaceInsertRepeat?: boolean,
193193
exitVisualBlock?: boolean,
194194
isEdit?: boolean,
195-
repeatOverride?: number
195+
repeatOverride?: number,
196+
noremap?: boolean,
196197
}
197198
export type motionCommand = allCommands & {
198199
type: 'motion',
@@ -223,7 +224,7 @@ export type operatorMotionCommand = allCommands & {
223224
operator: string,
224225
motionArgs?: MotionArgsPartial,
225226
operatorArgs?: OperatorArgs,
226-
operatorMotionArgs?: { [arg: string]: boolean | string }
227+
operatorMotionArgs?: { [arg: string]: boolean | string },
227228
}
228229
export type idleCommand = allCommands & { type: 'idle' }
229230
export type exCommand = allCommands & { type: 'ex' }

src/vim.js

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ export function initVim(CodeMirror) {
133133
{ keys: '<C-u>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }},
134134
{ keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
135135
{ keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
136-
{keys: "g$", type: "motion", motion: "moveToEndOfDisplayLine"},
137-
{keys: "g^", type: "motion", motion: "moveToStartOfDisplayLine"},
138-
{keys: "g0", type: "motion", motion: "moveToStartOfDisplayLine"},
136+
{ keys: "g$", type: "motion", motion: "moveToEndOfDisplayLine" },
137+
{ keys: "g^", type: "motion", motion: "moveToStartOfDisplayLine" },
138+
{ keys: "g0", type: "motion", motion: "moveToStartOfDisplayLine" },
139139
{ keys: '0', type: 'motion', motion: 'moveToStartOfLine' },
140140
{ keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' },
141141
{ keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }},
@@ -257,6 +257,7 @@ export function initVim(CodeMirror) {
257257
// Ex command
258258
{ keys: ':', type: 'ex' }
259259
];
260+
var usedKeys = Object.create(null);
260261
var defaultKeymapLength = defaultKeymap.length;
261262

262263
/**
@@ -972,7 +973,7 @@ export function initVim(CodeMirror) {
972973
if (vim.insertMode) { command = handleKeyInsertMode(); }
973974
else { command = handleKeyNonInsertMode(); }
974975
if (command === false) {
975-
return !vim.insertMode && key.length === 1 ? function() { return true; } : undefined;
976+
return !vim.insertMode && (key.length === 1 || (CodeMirror.isMac && /<A-.>/.test(key)))? function() { return true; } : undefined;
976977
} else if (command === true) {
977978
// TODO: Look into using CodeMirror's multi-key handling.
978979
// Return no-op since we are caching the key. Counts as handled, but
@@ -1150,7 +1151,7 @@ export function initVim(CodeMirror) {
11501151
// on mac many characters are entered as option- combos
11511152
// (e.g. on swiss keyboard { is option-8)
11521153
// so we ignore lonely A- modifier for keypress event on mac
1153-
if (CodeMirror.isMac && e.altKey && !e.metaKey && !e.ctrlKey) {
1154+
if (CodeMirror.isMac && name == "A-" && key.length == 1) {
11541155
name = name.slice(2);
11551156
}
11561157
if ((name || key.length > 1) && e.shiftKey) { name += 'S-'; }
@@ -1159,10 +1160,16 @@ export function initVim(CodeMirror) {
11591160
if (langmap.keymap && key in langmap.keymap) {
11601161
if (langmap.remapCtrl != false || !name)
11611162
key = langmap.keymap[key];
1162-
} else if (key.charCodeAt(0) > 255) {
1163-
var code = e.code?.slice(-1) || "";
1164-
if (!e.shiftKey) code = code.toLowerCase();
1165-
if (code) key = code;
1163+
} else if (key.charCodeAt(0) > 128) {
1164+
if (!usedKeys[key]) {
1165+
var code = e.code?.slice(-1) || "";
1166+
if (!e.shiftKey) code = code.toLowerCase();
1167+
if (code) {
1168+
key = code;
1169+
// also restore A- for mac
1170+
if (!name && e.altKey) name = 'A-'
1171+
}
1172+
}
11661173
}
11671174
}
11681175

@@ -5663,15 +5670,15 @@ export function initVim(CodeMirror) {
56635670
}
56645671
} else {
56655672
// Key to key or ex mapping
5673+
/**@type {vimKey} */
56665674
var mapping = {
56675675
keys: lhs,
56685676
type: 'keyToKey',
56695677
toKeys: rhs,
56705678
noremap: !!noremap
56715679
};
56725680
if (ctx) { mapping.context = ctx; }
5673-
// @ts-ignore
5674-
defaultKeymap.unshift(mapping);
5681+
_mapCommand(mapping);
56755682
}
56765683
}
56775684
/**@type {(lhs: string, ctx: string) => boolean|void} */
@@ -5691,6 +5698,7 @@ export function initVim(CodeMirror) {
56915698
if (keys == defaultKeymap[i].keys
56925699
&& defaultKeymap[i].context === ctx) {
56935700
defaultKeymap.splice(i, 1);
5701+
removeUsedKeys(keys);
56945702
return true;
56955703
}
56965704
}
@@ -6472,6 +6480,25 @@ export function initVim(CodeMirror) {
64726480
/** @arg {vimKey} command*/
64736481
function _mapCommand(command) {
64746482
defaultKeymap.unshift(command);
6483+
if (command.keys) addUsedKeys(command.keys);
6484+
}
6485+
6486+
/** @arg {string} keys */
6487+
function addUsedKeys(keys) {
6488+
keys.split(/(<(?:[CSMA]-)*\w+>|.)/i).forEach(function(part) {
6489+
if (part) {
6490+
if (!usedKeys[part]) usedKeys[part] = 0;
6491+
usedKeys[part]++;
6492+
}
6493+
});
6494+
}
6495+
6496+
/** @arg {string} keys */
6497+
function removeUsedKeys(keys) {
6498+
keys.split(/(<(?:[CSMA]-)*\w+>|.)/i).forEach(function(part) {
6499+
if (usedKeys[part])
6500+
usedKeys[part]--;
6501+
});
64756502
}
64766503

64776504
/**

test/vim_test.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5605,6 +5605,41 @@ testVim('option_key_on_mac', function(cm, vim, helpers) {
56055605
helpers.assertCursorAt(3, 0);
56065606
typeKey.optionTextInput('8', '{');
56075607
helpers.assertCursorAt(0, 0);
5608+
5609+
typeKey.utfTextInput('d', 'д');
5610+
typeKey.utfTextInput('l', 'л');
5611+
typeKey.utfTextInput('d', 'д');
5612+
typeKey.utfTextInput('G', 'Г');
5613+
eq('', cm.getValue());
5614+
helpers.doKeys('"-p');
5615+
eq('0', cm.getValue());
5616+
//TODO bug in paste
5617+
helpers.doKeys('l');
5618+
helpers.assertCursorAt(0, 0);
5619+
5620+
// map A-v
5621+
helpers.doEx(':map <A-v> i<lt>A-v><Esc>');
5622+
5623+
// verify that replace still works
5624+
typeKey.utfTextInput('r', 'Ռ');
5625+
typeKey.optionTextInput('v', '√');
5626+
eq('√', cm.getValue());
5627+
5628+
typeKey.optionTextInput('v', '√');
5629+
eq('<A-v>√', cm.getValue());
5630+
5631+
helpers.doEx(':map √ i√G<Esc>');
5632+
typeKey.optionTextInput('v', '√');
5633+
eq('<A-v√G>√', cm.getValue());
5634+
5635+
helpers.doEx(':unmap √');
5636+
typeKey.optionTextInput('v', '√');
5637+
eq('<A-v√<A-v>G>√', cm.getValue());
5638+
5639+
helpers.doEx(':unmap <A-v>');
5640+
typeKey.optionTextInput('v', '√');
5641+
eq('<A-v√<A-v>G>√', cm.getValue());
5642+
56085643
CodeMirror.isMac = false;
56095644
}, { value: '0\n1\n2\n\n\n3\n4\n' });
56105645

@@ -5874,9 +5909,9 @@ var typeKey = function() {
58745909
}
58755910
var key = keyCodeToKey[(shift ? "s-" : "") + keyCode];
58765911

5877-
if (options && options.macAltText) {
5878-
alt = true;
5879-
text = key = options.macAltText;
5912+
if (options && options.text) {
5913+
alt = options.altKey;
5914+
text = key = options.text;
58805915
isTextInput = true;
58815916
}
58825917

@@ -5990,7 +6025,11 @@ var typeKey = function() {
59906025
// emulates option-9 inputting } on mac swiss keyboard
59916026
type.optionTextInput = function(letter, altText) {
59926027
reset();
5993-
sendKey(letter, {macAltText: altText});
6028+
sendKey(letter, {text: altText, altKey: true});
6029+
};
6030+
6031+
type.utfTextInput = function(letter, altText) {
6032+
sendKey(letter, {text: altText});
59946033
};
59956034

59966035
type.clipboard = {};

0 commit comments

Comments
 (0)