JSのkeyをVim key codeに変換するscript
{key: 'A', shiftKey: true, ctrlKey: true}
などを <C-A>
に変換するscript
script.jsimport {mouseCodes, normalKeys, specialKeys} from './characters.js';
→
<
は \<
になる
\
は \\
になる
script.jsexport function js2vim({key, button, ctrlKey, shiftKey, altKey}) {
let code = '';
// マウスクリックの場合
if (button) {
code = button < 3 ? mouseCodes[button] : 'otherMouse';
// キーボード入力の場合
} else if (key) {
// Processは無視
if (key === 'Process') return undefined;
switch(key) {
// 修飾キーのみの場合は無視
case 'Shift':
case 'Control':
case 'Alt':
return undefined;
default:
if (!(key in specialKeys) && !(key in normalKeys)) {
const json = {key, ctrlKey, shiftKey, altKey};
throw Error(`Invalid key name: ${JSON.stringify(json)}`);
}
code = specialKeys[key] ?? normalKeys[key];
break;
}
} else {
return undefined;
}
// 修飾キーをつける
// どれか一つのmeta keyしか有効にしない
if (altKey) return `<A-${code}>`;
if (ctrlKey) return `<C-${code}>`;
// Shift keyは印字可能キー以外につける
if (shiftKey && !(key in normalKeys)) return `<S-${code}>`;
return (key in specialKeys) ? `<${code}>` : code;
}
→
script.jsconst rMouseCodes = reverse(mouseCodes);
const rNormalKeys = reverse(normalKeys);
const rSpecialKeys = reverse(specialKeys);
export function vim2js(keyString) {
const {meta, key} = parse(keyString);
if (key in rMouseCodes) {
const button = rMouseCodes[key];
switch (meta) {
case 'A':
return {button, altKey: meta};
case 'S':
return {button, shiftKey: meta};
case 'C':
return {button, ctrlKey: meta};
}
}
const newKey = rNormalKeys[key] ?? rNormalKeys[key];
if (newKey === undefined) throw Error(`Invalid key notation: ${JSON.stringify({notation: keyString, meta, key})}`);
switch (meta) {
case 'A':
return {key: newKey, altKey: meta};
case 'S':
return {key: newKey, shiftKey: meta};
case 'C':
return {key: newKey, ctrlKey: meta};
}
}
// keyの解析
function parse(keyString) {
if (/^<[ASC]-(?:>|[^>]+)>$/.test(keyString)) {
const [meta, key] = keyString.match(/^<([ASC])-(>|[^>]+)>$/).slice(1);
return {meta, key};
}
if (/^<[^>]>$/.test(keyString)) {
return {key: keyString.slice(1, -1)};
}
return {key: keyString};
}
function reverse(object) {
return Object.fromEntries(Object.entries(object).map(pair => pair.reverse()));
}
characters.jsconst range = (i, j) => [...Array(j + 1).keys()].slice(i, j + 1);
// <>無しのキー
export const normalKeys = Object.fromEntries(
range('!'.charCodeAt(0), '~'.charCodeAt(0))
.flatMap(code => {
const key = String.fromCodePoint(code);
if (key === '<') return [['<', '\\<']];
if (key === '\\') return [['\\', '\\\\']];
return [[key, key]];
})
);
// <>ありのキー
export const specialKeys = {
Backspace: 'BS',
Enter: 'CR',
Delete: 'Del',
Escape: 'Esc',
' ': 'Space',
ArrowLeft: 'Left',
ArrowUp: 'Up',
ArrowRight: 'Right',
ArrowDown: 'Down',
...Object.fromEntries([
'Tab', 'PageUp', 'PageDown', 'End', 'Home',
...range(1, 12).map(i => `F${i + 1}`),
].map(key => [key, key])),
};
export const keyboardCodes = {...specialKeys, ...normalKeys};
export const mouseCodes = {
0: 'LeftMouse',
1: 'MiddleMouse',
2: 'RightMouse',
};
test code
jsimport('/api/code/programming-notes/JSのkeyをVim_key_codeに変換するscript/test1.js');
test1.jsimport {js2vim} from './script.js';
const chars = '!"#$%&\'()~=~|QWERTYUIOP`{ASDFGHJKL+*}ZXCVBNM<>?_qwertyuiop@[asdfghjkl;:]zxcvbnm,./\\-^';
console.log(chars.split('').map(key => {
const code = js2vim({key, shiftKey: true});
return {code, judge: code === key};
}));
console.log(chars.split('').map(key => {
const code = js2vim({key, altKey: true, ctrlKey: true});
return {code, judge: code === `<A-${key}>`};
}));
console.log(chars.split('').map(key => {
const code = js2vim({key, ctrlKey: true});
return {code, judge: code === `<C-${key}>`};
}));