feat: cv2

This commit is contained in:
2026-01-09 14:42:33 +01:00
parent b9c6c05819
commit 5371b9d305
40 changed files with 2356 additions and 2217 deletions

View File

@@ -1,72 +1,39 @@
import { KEYMAP_CATEGORIES, KEYMAP_CODES } from "$lib/serial/keymap-codes";
import type {
Completion,
CompletionSection,
CompletionSource,
} from "@codemirror/autocomplete";
import { derived, get } from "svelte/store";
import { actionToValue, canUseIdAsString } from "./action-serializer";
import {
EditorView,
ViewPlugin,
ViewUpdate,
type PluginValue,
} from "@codemirror/view";
import { syntaxTree } from "@codemirror/language";
import type { EditorState } from "@codemirror/state";
const completionSections = derived(
KEYMAP_CATEGORIES,
(categories) =>
new Map(
categories.map(
(category) =>
[
category,
{
name: category.name,
} satisfies CompletionSection,
] as const,
),
),
);
export function actionAutocompletePlugin(
query: (query: string | undefined) => void,
) {
return ViewPlugin.fromClass(
class implements PluginValue {
constructor(readonly view: EditorView) {}
export const actionAutocompleteItems = derived(
[KEYMAP_CODES, completionSections],
([codes, sections]) =>
codes
.values()
.map((info) => {
const canUseId = canUseIdAsString(info);
const completionValue =
(canUseId && info.id) ||
`0x${info.code.toString(16).padStart(2, "0")}`;
return {
label:
[
canUseId || !info.id ? undefined : `"${info.id}"`,
info.title,
info.variant?.replace(/^[a-z]/g, (c) => c.toUpperCase()),
]
.filter(Boolean)
.join(" ") || completionValue,
detail: actionToValue(info),
section: info.category ? sections.get(info.category) : undefined,
info: info.description,
type: "keyword",
apply: completionValue + ">",
} satisfies Completion;
})
.filter(
(item) => typeof item.label === "string" && item.apply !== undefined,
)
.toArray(),
);
update(update: ViewUpdate) {
query(this.resolveAutocomplete(update.state));
}
export const actionAutocomplete = ((context) => {
let word = context.tokenBefore([
"ExplicitDelimStart",
"ActionId",
"HexNumber",
"DecimalNumber",
]);
if (!word) return null;
console.log(get(actionAutocompleteItems));
return {
from: word.type.name === "ExplicitDelimStart" ? word.to : word.from,
validFor: /^<?[a-zA-Z0-9_]*$/,
options: get(actionAutocompleteItems),
};
}) satisfies CompletionSource;
resolveAutocomplete(state: EditorState): string | undefined {
if (state.selection.ranges.length !== 1) return;
const from = state.selection.ranges[0]!.from;
const to = state.selection.ranges[0]!.to;
if (from !== to) return;
const tree = syntaxTree(state);
const node = tree.resolveInner(from, -1).parent;
if (node?.name !== "ExplicitAction") return;
if (node.getChild("ExplicitDelimEnd")) return;
const queryNode = node.getChild("ExplicitDelimStart")?.nextSibling;
return (
(queryNode
? state.doc.sliceString(queryNode.from, queryNode.to)
: undefined) || undefined
);
}
},
);
}