feat(keymap-upgrader): Upgrade renamed nodes

Added an upgrade function to fix renamed behavior nodes in the unlikely
event that someone was changing behavior settings this way instead of
using references.
This commit is contained in:
Joel Spadin 2024-01-22 15:10:52 -06:00
parent bf5284b3b9
commit 08d9391a8a
4 changed files with 119 additions and 1 deletions

View File

@ -4,6 +4,7 @@ import { applyEdits, Range } from "./textedit";
import { upgradeBehaviors } from "./behaviors";
import { upgradeHeaders } from "./headers";
import { upgradeKeycodes } from "./keycodes";
import { upgradeNodeNames } from "./nodes";
import { upgradeProperties } from "./properties";
export { initParser } from "./parser";
@ -12,6 +13,7 @@ const upgradeFunctions = [
upgradeBehaviors,
upgradeHeaders,
upgradeKeycodes,
upgradeNodeNames,
upgradeProperties,
];

View File

@ -0,0 +1,49 @@
import type { Tree } from "web-tree-sitter";
import { findDevicetreeNode } from "./parser";
import { TextEdit } from "./textedit";
// Map of { "deprecated path": "replacement name" } for devicetree nodes.
// Relocating nodes to another place in the tree is not supported.
const NODES = {
"/behaviors/behavior_backlight": "bcklight",
"/behaviors/behavior_caps_word": "caps_word",
"/behaviors/behavior_ext_power": "extpower",
"/behaviors/behavior_key_press": "key_press",
"/behaviors/behavior_key_repeat": "key_repeat",
"/behaviors/behavior_key_toggle": "key_toggle",
"/behaviors/behavior_layer_tap": "layer_tap",
"/behaviors/behavior_mod_tap": "mod_tap",
"/behaviors/behavior_momentary_layer": "momentary_layer",
"/behaviors/behavior_none": "none",
"/behaviors/behavior_outputs": "outputs",
"/behaviors/behavior_behavior_reset": "sysreset",
"/behaviors/behavior_reset_dfu": "bootload",
"/behaviors/behavior_rgb_underglow": "rgb_ug",
"/behaviors/behavior_sensor_rotate_key_press": "enc_key_press",
"/behaviors/behavior_sticky_key": "sticky_key",
"/behaviors/behavior_sticky_layer": "sticky_layer",
"/behaviors/behavior_to_layer": "to_layer",
"/behaviors/behavior_toggle_layer": "toggle_layer",
"/behaviors/behavior_transparent": "transparent",
"/behaviors/macro_control_mode_tap": "macro_tap",
"/behaviors/macro_control_mode_press": "macro_press",
"/behaviors/macro_control_mode_release": "macro_release",
"/behaviors/macro_control_tap_time": "macro_tap_time",
"/behaviors/macro_control_wait_time": "macro_wait_time",
};
export function upgradeNodeNames(tree: Tree) {
const edits: TextEdit[] = [];
for (const [path, newName] of Object.entries(NODES)) {
for (const node of findDevicetreeNode(tree, path)) {
const name = node.childForFieldName("name");
if (name) {
edits.push(TextEdit.fromNode(name, newName));
}
}
}
return edits;
}

View File

@ -54,3 +54,70 @@ export function captureHasText(
const node = findCapture(name, captures);
return node?.text === text;
}
/**
* Get a list of SyntaxNodes representing a devicetree node with the given path.
* (The same node may be listed multiple times within a file.)
*
* @param path Absolute path to the node (must start with "/")
*/
export function findDevicetreeNode(
tree: Parser.Tree,
path: string
): Parser.SyntaxNode[] {
const query = Devicetree.query("(node) @node");
const matches = query.matches(tree.rootNode);
const result: Parser.SyntaxNode[] = [];
for (const { captures } of matches) {
const node = findCapture("node", captures);
if (node && getDevicetreeNodePath(node) === path) {
result.push(node);
}
}
return result;
}
export function getDevicetreeNodePath(node: Parser.SyntaxNode | null) {
const parts = getDevicetreeNodePathParts(node);
if (parts.length === 0) {
return "";
}
if (parts.length === 1) {
return parts[0];
}
const path = parts.join("/");
// The top-level node should be named "/", which is a special case since the
// path should not start with "//".
return parts[0] === "/" ? path.substring(1) : path;
}
export function getDevicetreeNodePathParts(
node: Parser.SyntaxNode | null
): string[] {
// There may be intermediate syntax nodes between devicetree nodes, such as
// #if blocks, so if we aren't currently on a "node" node, traverse up the
// tree until we find one.
const dtnode = getContainingDevicetreeNode(node);
if (!dtnode) {
return [];
}
const name = dtnode.childForFieldName("name")?.text ?? "";
return [...getDevicetreeNodePathParts(dtnode.parent), name];
}
function getContainingDevicetreeNode(node: Parser.SyntaxNode | null) {
while (node && node.type !== "node") {
node = node.parent;
}
return node;
}

View File

@ -8,6 +8,6 @@
"strict": true,
"noEmit": true,
"target": "ES6",
"lib": ["ES2019.Array", "DOM", "DOM.Iterable"]
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}