forked from kofal.net/zmk
* fix(docs): Fix TypeScript import diagnostics Set the TypeScript module resolution mode to "bundler" so it correctly resolves imports in .ts files instead of showing errors in IDEs. * chore(docs): Update web-tree-sitter Updated web-tree-sitter, and switched from having a copy of tree-sitter-devicetree.wasm in the repo to using the latest version of the tree-sitter-devicetree module, which now contains the .wasm file. * fix(docs): Make keymap upgrader resize vertically only Changed the textarea in the keymap ugprader to allow only vertical resizing. This better fits the layout of the page, and it prevents you from shrinking the box horizontally to the point where it doesn't fit anything.
191 lines
5.1 KiB
TypeScript
191 lines
5.1 KiB
TypeScript
import Parser from "web-tree-sitter";
|
|
|
|
const TREE_SITTER_WASM_URL = new URL(
|
|
"/node_modules/web-tree-sitter/tree-sitter.wasm",
|
|
import.meta.url
|
|
);
|
|
|
|
const TREE_SITTER_DEVICETREE_WASM_URL = new URL(
|
|
"/node_modules/tree-sitter-devicetree/tree-sitter-devicetree.wasm",
|
|
import.meta.url
|
|
);
|
|
|
|
export let Devicetree: Parser.Language;
|
|
|
|
export async function initParser() {
|
|
await Parser.init({
|
|
locateFile: (path: string, prefix: string) => {
|
|
// When locating tree-sitter.wasm, use a path that Webpack can map to the correct URL.
|
|
if (path == "tree-sitter.wasm") {
|
|
return TREE_SITTER_WASM_URL.href;
|
|
}
|
|
return prefix + path;
|
|
},
|
|
});
|
|
Devicetree = await Parser.Language.load(TREE_SITTER_DEVICETREE_WASM_URL.href);
|
|
}
|
|
|
|
export function createParser() {
|
|
if (!Devicetree) {
|
|
throw new Error("Parser not loaded. Call initParser() first.");
|
|
}
|
|
|
|
const parser = new Parser();
|
|
parser.setLanguage(Devicetree);
|
|
return parser;
|
|
}
|
|
|
|
/**
|
|
* Returns the node for the named capture.
|
|
*/
|
|
export function findCapture(name: string, captures: Parser.QueryCapture[]) {
|
|
for (const c of captures) {
|
|
if (c.name === name) {
|
|
return c.node;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the node for the named capture exists and has the given text.
|
|
*/
|
|
export function captureHasText(
|
|
name: string,
|
|
captures: Parser.QueryCapture[],
|
|
text: string
|
|
) {
|
|
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.
|
|
*
|
|
* This function does not evaluate which node a reference points to, so given
|
|
* a file containing "/ { foo: bar {}; }; &foo {};" searching for "&foo" will
|
|
* return the "&foo {}" node but not "foo: bar {}".
|
|
*
|
|
* @param path Path to the node to find. May be an absolute path such as
|
|
* "/foo/bar", a node reference such as "&foo", or a node reference followed by
|
|
* a relative path such as "&foo/bar".
|
|
*/
|
|
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 interface FindPropertyOptions {
|
|
/** Search in children of the given node as well */
|
|
recursive?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Find all instances of a devicetree property with the given name which are
|
|
* descendants of the given syntax node.
|
|
*
|
|
* @param node Any syntax node
|
|
*/
|
|
export function findDevicetreeProperty(
|
|
node: Parser.SyntaxNode,
|
|
name: string,
|
|
options: FindPropertyOptions & { recursive: true }
|
|
): Parser.SyntaxNode[];
|
|
|
|
/**
|
|
* Find a devicetree node's property with the given name, or null if it doesn't
|
|
* have one.
|
|
*
|
|
* @note If the node contains multiple instances of the same property, this
|
|
* returns the last once, since that is the one that will actually be applied.
|
|
*
|
|
* @param node A syntax node for a devicetree node
|
|
*/
|
|
export function findDevicetreeProperty(
|
|
node: Parser.SyntaxNode,
|
|
name: string,
|
|
options?: FindPropertyOptions
|
|
): Parser.SyntaxNode | null;
|
|
|
|
export function findDevicetreeProperty(
|
|
node: Parser.SyntaxNode,
|
|
name: string,
|
|
options?: FindPropertyOptions
|
|
): Parser.SyntaxNode[] | Parser.SyntaxNode | null {
|
|
const query = Devicetree.query(
|
|
`(property name: (identifier) @name (#eq? @name "${name}")) @prop`
|
|
);
|
|
const matches = query.matches(node);
|
|
const props = matches.map(({ captures }) => findCapture("prop", captures)!);
|
|
|
|
if (options?.recursive) {
|
|
return props;
|
|
}
|
|
|
|
// The query finds all descendants. Filter to just the properties that belong
|
|
// to the given devicetree node.
|
|
const childProps = props.filter((prop) =>
|
|
getContainingDevicetreeNode(prop)?.equals(node)
|
|
);
|
|
|
|
// Sort in descending order to select the last instance of the property.
|
|
childProps.sort((a, b) => b.startIndex - a.startIndex);
|
|
return childProps[0] ?? null;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
export function getContainingDevicetreeNode(node: Parser.SyntaxNode | null) {
|
|
while (node && node.type !== "node") {
|
|
node = node.parent;
|
|
}
|
|
return node;
|
|
}
|