wip live coding mode

This commit is contained in:
2025-10-15 02:53:48 +02:00
parent a4432fa3d9
commit 46925f5c2e
12 changed files with 1599 additions and 24 deletions

View File

@ -0,0 +1,111 @@
import { EditorView, Decoration } from '@codemirror/view';
import { EditorState, StateField, StateEffect } from '@codemirror/state';
interface EvalBlock {
text: string;
from: number | null;
to: number | null;
}
type FlashRange = [number, number];
const setFlash = StateEffect.define<FlashRange | null>();
const defaultStyle = {
'background-color': '#FFCA2880',
};
const styleObjectToString = (styleObj: Record<string, string>): string =>
Object.entries(styleObj)
.map(([k, v]) => `${k}:${v}`)
.join(';');
export const flash = (
view: EditorView,
from: number | null,
to: number | null,
timeout: number = 150,
) => {
if (from === null || to === null) return;
view.dispatch({ effects: setFlash.of([from, to]) });
setTimeout(() => {
view.dispatch({ effects: setFlash.of(null) });
}, timeout);
};
export const flashField = (style: Record<string, string> = defaultStyle) =>
StateField.define({
create() {
return Decoration.none;
},
update(flash, tr) {
try {
for (let e of tr.effects) {
if (e.is(setFlash)) {
if (e.value) {
const [from, to] = e.value;
const mark = Decoration.mark({
attributes: { style: styleObjectToString(style) },
});
flash = Decoration.set([mark.range(from, to)]);
} else {
flash = Decoration.set([]);
}
}
}
return flash;
} catch (err) {
console.warn('flash error', err);
return flash;
}
},
provide: (f) => EditorView.decorations.from(f),
});
export function getSelection(state: EditorState): EvalBlock {
if (state.selection.main.empty) return { text: '', from: null, to: null };
let { from, to } = state.selection.main;
let text = state.doc.sliceString(from, to);
return { text, from, to };
}
export function getLine(state: EditorState): EvalBlock {
const line = state.doc.lineAt(state.selection.main.from);
let { from, to } = line;
let text = state.doc.sliceString(from, to);
return { text, from, to };
}
export function getBlock(state: EditorState): EvalBlock {
let { doc, selection } = state;
let { text, number } = state.doc.lineAt(selection.main.from);
if (text.trim().length === 0) return { text: '', from: null, to: null };
let fromL, toL;
fromL = toL = number;
while (fromL > 1 && doc.line(fromL - 1).text.trim().length > 0) {
fromL -= 1;
}
while (toL < doc.lines && doc.line(toL + 1).text.trim().length > 0) {
toL += 1;
}
let { from } = doc.line(fromL);
let { to } = doc.line(toL);
text = state.doc.sliceString(from, to);
return { text, from, to };
}
export function getDocument(state: EditorState): EvalBlock {
const { from } = state.doc.line(1);
const { to } = state.doc.line(state.doc.lines);
const text = state.doc.sliceString(from, to);
return { text, from, to };
}