zmk/docs/src/keymap-upgrade/keycodes.ts
Joel Spadin 89fb8c23ce fix(keymap-upgrader): Filter key codes to bindings
Changed the key code upgrader to only replace codes that appear in
"bindings" properties. Modifier flags such as MOD_LCTL are no longer
valid as key codes, but they are still used in "mods" properties and
should not be replaced there.
2024-01-22 15:02:07 -06:00

169 lines
4 KiB
TypeScript

import type { SyntaxNode, Tree } from "web-tree-sitter";
import { Devicetree, findCapture } from "./parser";
import { getUpgradeEdits, TextEdit } from "./textedit";
// Map of { "DEPRECATED": "REPLACEMENT" } key codes.
const CODES = {
NUM_1: "N1",
NUM_2: "N2",
NUM_3: "N3",
NUM_4: "N4",
NUM_5: "N5",
NUM_6: "N6",
NUM_7: "N7",
NUM_8: "N8",
NUM_9: "N9",
NUM_0: "N0",
BKSP: "BSPC",
SPC: "SPACE",
EQL: "EQUAL",
TILD: "TILDE",
SCLN: "SEMI",
QUOT: "SQT",
GRAV: "GRAVE",
CMMA: "COMMA",
PRSC: "PSCRN",
SCLK: "SLCK",
PAUS: "PAUSE_BREAK",
PGUP: "PG_UP",
PGDN: "PG_DN",
RARW: "RIGHT",
LARW: "LEFT",
DARW: "DOWN",
UARW: "UP",
KDIV: "KP_DIVIDE",
KMLT: "KP_MULTIPLY",
KMIN: "KP_MINUS",
KPLS: "KP_PLUS",
UNDO: "K_UNDO",
CUT: "K_CUT",
COPY: "K_COPY",
PSTE: "K_PASTE",
VOLU: "K_VOL_UP",
VOLD: "K_VOL_DN",
CURU: "DLLR",
LPRN: "LPAR",
RPRN: "RPAR",
LCUR: "LBRC",
RCUR: "RBRC",
CRRT: "CARET",
PRCT: "PRCNT",
LABT: "LT",
RABT: "GT",
COLN: "COLON",
KSPC: null,
ATSN: "AT",
BANG: "EXCL",
LCTL: "LCTRL",
LSFT: "LSHIFT",
RCTL: "RCTRL",
RSFT: "RSHIFT",
M_NEXT: "C_NEXT",
M_PREV: "C_PREV",
M_STOP: "C_STOP",
M_EJCT: "C_EJECT",
M_PLAY: "C_PP",
M_MUTE: "C_MUTE",
M_VOLU: "C_VOL_UP",
M_VOLD: "C_VOL_DN",
GUI: "K_CMENU",
MOD_LCTL: "LCTRL",
MOD_LSFT: "LSHIFT",
MOD_LALT: "LALT",
MOD_LGUI: "LGUI",
MOD_RCTL: "RCTRL",
MOD_RSFT: "RSHIFT",
MOD_RALT: "RALT",
MOD_RGUI: "RGUI",
};
// Regex matching names of properties that can have keymap bindings.
const BINDINGS_PROPS = /^(bindings|sensor-bindings)$/;
/**
* Upgrades deprecated key code identifiers.
*/
export function upgradeKeycodes(tree: Tree) {
const edits: TextEdit[] = [];
const query = Devicetree.query("(identifier) @name");
const matches = query.matches(tree.rootNode);
for (const { captures } of matches) {
const node = findCapture("name", captures);
// Some of the codes are still valid to use in other properties such as
// "mods", so only replace those that are inside a "bindings" array.
if (node && isInBindingsArray(node)) {
edits.push(...getUpgradeEdits(node, CODES, keycodeReplaceHandler));
}
}
return edits;
}
function keycodeReplaceHandler(node: SyntaxNode, replacement: string | null) {
if (replacement) {
return [TextEdit.fromNode(node, replacement)];
}
const nodes = findBehaviorNodes(node);
if (nodes.length === 0) {
console.warn(
`Found deprecated code "${node.text}" but it is not a parameter to a behavior`
);
return [TextEdit.fromNode(node, `/* "${node.text}" no longer exists */`)];
}
const oldText = nodes.map((n) => n.text).join(" ");
const newText = `&none /* "${oldText}" no longer exists */`;
const startIndex = nodes[0].startIndex;
const endIndex = nodes[nodes.length - 1].endIndex;
return [new TextEdit(startIndex, endIndex, newText)];
}
/**
* Given a parameter to a keymap behavior, returns a list of nodes beginning
* with the behavior and including all parameters.
* Returns an empty array if no behavior was found.
*/
function findBehaviorNodes(paramNode: SyntaxNode) {
// Walk backwards from the given parameter to find the behavior reference.
let behavior = paramNode.previousNamedSibling;
while (behavior && behavior.type !== "reference") {
behavior = behavior.previousNamedSibling;
}
if (!behavior) {
return [];
}
// Walk forward from the behavior to collect all its parameters.
let nodes = [behavior];
let param = behavior.nextNamedSibling;
while (param && param.type !== "reference") {
nodes.push(param);
param = param.nextNamedSibling;
}
return nodes;
}
/**
* Given a identifier, returns whether it is inside a "bindings" property value.
*/
function isInBindingsArray(identifier: SyntaxNode) {
let node = identifier.parent;
while (node) {
if (node.type === "property") {
return !!node.childForFieldName("name")?.text.match(BINDINGS_PROPS);
}
node = node.parent;
}
return false;
}