starting work on highlights

This commit is contained in:
Parker TenBroeck 2026-01-12 11:00:48 -05:00
parent ba996ee942
commit 58fb1b956c
3 changed files with 73 additions and 1 deletions

View file

@ -2,6 +2,7 @@
import { import {
Decoration, Decoration,
DecorationSet,
EditorView, EditorView,
highlightActiveLine, highlightActiveLine,
highlightActiveLineGutter, highlightActiveLineGutter,
@ -10,7 +11,7 @@ import {
lineNumbers, lineNumbers,
} from "npm:@codemirror/view"; } from "npm:@codemirror/view";
import { EditorState, StateField, Text } from "npm:@codemirror/state"; import { EditorState, RangeSetBuilder, StateEffect, StateField, Text } from "npm:@codemirror/state";
import { import {
defaultKeymap, defaultKeymap,
history, 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<DecorationSet>({
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<Decoration>();
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<HighlightSpan[]>();
const eventBusConnection = StateField.define({ const eventBusConnection = StateField.define({
create(state) { create(state) {
const text = state.doc.toString(); const text = state.doc.toString();
@ -198,6 +250,7 @@ const state = EditorState.create({
keymap.of([...defaultKeymap, ...historyKeymap]), keymap.of([...defaultKeymap, ...historyKeymap]),
eventBusConnection, eventBusConnection,
highlightsField,
diagHover, diagHover,
EditorView.lineWrapping, EditorView.lineWrapping,
@ -223,3 +276,11 @@ bus.on("controls/editor/set_text", ({ text }) => {
bus.on("example/selected", ({ example }) => { bus.on("example/selected", ({ example }) => {
bus.emit("controls/editor/set_text", { text: example.machine }); 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" },
])

0
web/root/src/focus.ts Normal file
View file

View file

@ -132,3 +132,14 @@
text-underline-offset: 2px; 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)); }