refactor(ui): debounce variable highlighting scanning in CodeMirror

Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com>
This commit is contained in:
google-labs-jules[bot] 2026-07-28 23:49:36 +00:00
parent f5b1fc038b
commit 284e16c0e1
2 changed files with 39 additions and 5 deletions

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "summarum", "name": "summarum",
"version": "1.2.1", "version": "1.2.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "summarum", "name": "summarum",
"version": "1.2.1", "version": "1.2.2",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.18.0", "@codemirror/autocomplete": "^6.18.0",
"@codemirror/commands": "^6.8.0", "@codemirror/commands": "^6.8.0",

View file

@ -1,6 +1,6 @@
// CodeMirror editor wired to the engine: evaluates on change, highlights // CodeMirror editor wired to the engine: evaluates on change, highlights
// engine tokens, renders the results overlay, autocompletes phrases. // engine tokens, renders the results overlay, autocompletes phrases.
import { EditorView, ViewUpdate, Decoration, DecorationSet, keymap, drawSelection } from "@codemirror/view"; import { EditorView, ViewPlugin, ViewUpdate, Decoration, DecorationSet, keymap, drawSelection } from "@codemirror/view";
import { EditorState, StateEffect, StateField, RangeSetBuilder } from "@codemirror/state"; import { EditorState, StateEffect, StateField, RangeSetBuilder } from "@codemirror/state";
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"; import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
import { autocompletion, CompletionContext, CompletionResult, acceptCompletion } from "@codemirror/autocomplete"; import { autocompletion, CompletionContext, CompletionResult, acceptCompletion } from "@codemirror/autocomplete";
@ -99,15 +99,48 @@ function buildVarHighlight(state: EditorState): DecorationSet {
return builder.finish(); return builder.finish();
} }
const setVarHighlight = StateEffect.define<DecorationSet>();
const varHighlightField = StateField.define<DecorationSet>({ const varHighlightField = StateField.define<DecorationSet>({
create: buildVarHighlight, create: () => Decoration.none,
update(value, tr) { update(value, tr) {
if (tr.docChanged || tr.selection) return buildVarHighlight(tr.state); for (const e of tr.effects) {
if (e.is(setVarHighlight)) return e.value;
}
if (tr.docChanged) return value.map(tr.changes);
return value; return value;
}, },
provide: (f) => EditorView.decorations.from(f), provide: (f) => EditorView.decorations.from(f),
}); });
const varHighlightPlugin = ViewPlugin.fromClass(
class {
timeout: ReturnType<typeof setTimeout> | null = null;
constructor(view: EditorView) {
this.schedule(view);
}
update(update: ViewUpdate) {
if (update.docChanged || update.selectionSet) {
this.schedule(update.view);
}
}
schedule(view: EditorView) {
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
const decos = buildVarHighlight(view.state);
view.dispatch({ effects: setVarHighlight.of(decos) });
}, 150);
}
destroy() {
if (this.timeout) clearTimeout(this.timeout);
}
}
);
function buildSparkline(pts: number[], top: number): SVGSVGElement { function buildSparkline(pts: number[], top: number): SVGSVGElement {
const NS = "http://www.w3.org/2000/svg"; const NS = "http://www.w3.org/2000/svg";
const W = 80, H = 20, pad = 2; const W = 80, H = 20, pad = 2;
@ -201,6 +234,7 @@ export class SumEditor {
resultsField, resultsField,
highlightField, highlightField,
varHighlightField, varHighlightField,
varHighlightPlugin,
EditorView.lineWrapping, EditorView.lineWrapping,
drawSelection({ cursorBlinkRate: 1000 }), drawSelection({ cursorBlinkRate: 1000 }),
// CM injects its base styles at runtime after app.css, so layout // CM injects its base styles at runtime after app.css, so layout