From 58fb1b956c817fade86257f6b002a5d377c8d776 Mon Sep 17 00:00:00 2001 From: Parker TenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:00:48 -0500 Subject: [PATCH] starting work on highlights --- web/root/src/editor.ts | 63 +++++++++++++++++++++++++++++++++++++- web/root/src/focus.ts | 0 web/root/style/editor.scss | 11 +++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 web/root/src/focus.ts diff --git a/web/root/src/editor.ts b/web/root/src/editor.ts index d3594be..c3340e5 100644 --- a/web/root/src/editor.ts +++ b/web/root/src/editor.ts @@ -2,6 +2,7 @@ import { Decoration, + DecorationSet, EditorView, highlightActiveLine, highlightActiveLineGutter, @@ -10,7 +11,7 @@ import { lineNumbers, } from "npm:@codemirror/view"; -import { EditorState, StateField, Text } from "npm:@codemirror/state"; +import { EditorState, RangeSetBuilder, StateEffect, StateField, Text } from "npm:@codemirror/state"; import { defaultKeymap, history, @@ -45,6 +46,57 @@ function compile( } } + +export type HighlightKind = "focus" | "success" | "warning" | "error"; + +export type HighlightSpan = { + from: number; + to: number; + kind: HighlightKind; +}; + +function decoForKind(kind: HighlightKind) { + // Use a class per kind so each gets a distinct color via CSS + return Decoration.mark({ class: `cm-highlight cm-highlight-${kind}` }); +} + +export function applyHighlights(view: EditorView, spans: HighlightSpan[]) { + view.dispatch({ effects: setHighlights.of(spans) }); +} + + + +export const highlightsField = StateField.define({ + create() { + return Decoration.none; + }, + + update(highlights, tr) { + // Keep highlights aligned with document edits + // highlights = highlights.map(tr.changes); + + for (const e of tr.effects) { + if (e.is(setHighlights)) { + const spans = e.value; + + const builder = new RangeSetBuilder(); + for (const s of spans) { + const from = Math.max(0, Math.min(s.from, tr.state.doc.length)); + const to = Math.max(0, Math.min(s.to, tr.state.doc.length)); + if (to > from) builder.add(from, to, decoForKind(s.kind)); + } + highlights = builder.finish(); + } + } + + return highlights; + }, + + provide: (f) => EditorView.decorations.from(f), +}); + +export const setHighlights = StateEffect.define(); + const eventBusConnection = StateField.define({ create(state) { const text = state.doc.toString(); @@ -198,6 +250,7 @@ const state = EditorState.create({ keymap.of([...defaultKeymap, ...historyKeymap]), eventBusConnection, + highlightsField, diagHover, EditorView.lineWrapping, @@ -223,3 +276,11 @@ bus.on("controls/editor/set_text", ({ text }) => { bus.on("example/selected", ({ example }) => { bus.emit("controls/editor/set_text", { text: example.machine }); }); + + +applyHighlights(editor, [ + { from: 0, to: 10, kind: "focus" }, + { from: 10, to: 20, kind: "success" }, + { from: 20, to: 30, kind: "warning" }, + { from: 30, to: 40, kind: "error" }, +]) \ No newline at end of file diff --git a/web/root/src/focus.ts b/web/root/src/focus.ts new file mode 100644 index 0000000..e69de29 diff --git a/web/root/style/editor.scss b/web/root/style/editor.scss index 6cd740a..834cafa 100644 --- a/web/root/style/editor.scss +++ b/web/root/style/editor.scss @@ -132,3 +132,14 @@ text-underline-offset: 2px; } + + +.cm-highlight { + border-radius: 4px; + padding: 0 1px; +} + +.cm-highlight-warning { background: color-mix(in srgb, var(--warning) 40%, var(--bg-0)); } +.cm-highlight-focus { background: color-mix(in srgb, var(--focus) 40%, var(--bg-0)); } +.cm-highlight-success { background: color-mix(in srgb, var(--success) 40%, var(--bg-0)); } +.cm-highlight-error { background: color-mix(in srgb, var(--error) 40%, var(--bg-0)); } \ No newline at end of file