* feat(files): edit workspace files on web Keep source buffers synchronized with host file changes and require an explicit overwrite or reload when revisions diverge. * feat(panels): surface and protect modified tabs Expose tooltip and modification state through the generic panel boundary so tabs can show stable metadata and guard every close route consistently. * fix(tests): use portable fake timeout handle * fix(files): harden editor conflict handling Preserve modified panel state across tab eviction, use precise revisions for optimistic writes, coalesce concurrent file watchers, and localize the editor interface. * fix(files): close editor concurrency gaps Coalesce clean reloads, preserve subscriber identities and file permissions, suspend pending saves during close confirmation, and carry precise revisions through file reads. * test(files): expect read revision metadata
88 lines
2.5 KiB
TypeScript
88 lines
2.5 KiB
TypeScript
import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
|
|
import {
|
|
bracketMatching,
|
|
defaultHighlightStyle,
|
|
indentOnInput,
|
|
syntaxHighlighting,
|
|
} from "@codemirror/language";
|
|
import { searchKeymap } from "@codemirror/search";
|
|
import {
|
|
EditorView,
|
|
drawSelection,
|
|
highlightActiveLine,
|
|
keymap,
|
|
lineNumbers,
|
|
} from "@codemirror/view";
|
|
import { createCodeMirrorHighlightStyle, type HighlightStyle } from "@getpaseo/highlight";
|
|
|
|
export interface EditorVisualTheme {
|
|
colorScheme: "light" | "dark";
|
|
background: string;
|
|
foreground: string;
|
|
cursor: string;
|
|
foregroundMuted: string;
|
|
border: string;
|
|
selection: string;
|
|
monoFont: string;
|
|
codeFontSize: number;
|
|
syntax: Record<HighlightStyle, string>;
|
|
}
|
|
|
|
export function editorBaseExtensions(onSave: () => void) {
|
|
return [
|
|
lineNumbers(),
|
|
history(),
|
|
drawSelection(),
|
|
indentOnInput(),
|
|
bracketMatching(),
|
|
highlightActiveLine(),
|
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
keymap.of([
|
|
{ key: "Mod-s", preventDefault: true, run: () => (onSave(), true) },
|
|
indentWithTab,
|
|
...defaultKeymap,
|
|
...historyKeymap,
|
|
...searchKeymap,
|
|
]),
|
|
];
|
|
}
|
|
|
|
export function editorTheme(theme: EditorVisualTheme) {
|
|
return [
|
|
EditorView.theme(
|
|
{
|
|
"&": {
|
|
height: "100%",
|
|
backgroundColor: theme.background,
|
|
color: theme.foreground,
|
|
fontFamily: theme.monoFont,
|
|
fontSize: `${theme.codeFontSize}px`,
|
|
},
|
|
".cm-scroller": {
|
|
overflow: "auto",
|
|
fontFamily: theme.monoFont,
|
|
lineHeight: "1.45",
|
|
},
|
|
".cm-content": { caretColor: theme.foreground, padding: "16px 0" },
|
|
".cm-cursor, .cm-dropCursor": { borderLeftColor: theme.cursor },
|
|
".cm-gutters": {
|
|
backgroundColor: theme.background,
|
|
color: theme.foregroundMuted,
|
|
borderRight: `1px solid ${theme.border}`,
|
|
},
|
|
".cm-activeLine": { backgroundColor: "transparent" },
|
|
".cm-activeLineGutter": { backgroundColor: "transparent", color: theme.foreground },
|
|
"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
|
|
backgroundColor: theme.selection,
|
|
},
|
|
".cm-selectionBackground, ::selection": {
|
|
backgroundColor: theme.selection,
|
|
},
|
|
"&.cm-focused": { outline: "none" },
|
|
},
|
|
{ dark: theme.colorScheme === "dark" },
|
|
),
|
|
syntaxHighlighting(createCodeMirrorHighlightStyle(theme.syntax)),
|
|
];
|
|
}
|