feat(editor-v2): editor-entry façade wiring all extensions
Replaces the Task 1 stub. Builds an EditorView with: - history, line numbers, active-line highlight, bracket matching, close brackets, indent-on-input - default + custom HighlightStyle - light/dark theme via matchMedia-driven Compartment with a prefers-color-scheme change listener - language via Compartment (swappable for the files-modal dropdown) - autocomplete via Compartment (only if vocab is provided) - keymap stack: closeBrackets, default, history, completion, indentWithTab Mounts the EditorView immediately before the textarea, hides the textarea. Exposes window.__editor.mount(textarea, opts) returning a controller with getValue / setContent / setLanguage / destroy. bash language comes via @codemirror/legacy-modes/mode/shell wrapped in StreamLanguage.define — same mechanism as srccfg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3440bbc131
commit
bfc8b82c00
1 changed files with 84 additions and 2 deletions
|
|
@ -1,2 +1,84 @@
|
|||
// Façade entry point. Populated in Task 4.
|
||||
window.__editor = { mount: () => { throw new Error('editor bundle not yet implemented'); } };
|
||||
import { EditorState, Compartment } from "@codemirror/state";
|
||||
import { EditorView, keymap, lineNumbers, highlightActiveLine } from "@codemirror/view";
|
||||
import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
|
||||
import { StreamLanguage, indentOnInput, bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
|
||||
import { closeBrackets, closeBracketsKeymap, completionKeymap } from "@codemirror/autocomplete";
|
||||
import { shell as shellLegacy } from "@codemirror/legacy-modes/mode/shell";
|
||||
|
||||
import { srccfgLanguage } from "./srccfg-mode.js";
|
||||
import { editorLightTheme, editorDarkTheme, editorHighlighting } from "./themes.js";
|
||||
import { autocompleteExtension } from "./autocomplete.js";
|
||||
|
||||
const bashLanguage = StreamLanguage.define(shellLegacy);
|
||||
|
||||
function pickLanguage(name) {
|
||||
if (name === "srccfg") return srccfgLanguage;
|
||||
if (name === "bash") return bashLanguage;
|
||||
return null; // "plain" / unknown → no language extension
|
||||
}
|
||||
|
||||
function pickThemeForMatchMedia(mm) {
|
||||
return mm.matches ? editorDarkTheme : editorLightTheme;
|
||||
}
|
||||
|
||||
function mount(textarea, { language = "plain", vocab = null } = {}) {
|
||||
const langCompartment = new Compartment();
|
||||
const themeCompartment = new Compartment();
|
||||
const autocompleteCompartment = new Compartment();
|
||||
|
||||
const lang = pickLanguage(language);
|
||||
const mm = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
const extensions = [
|
||||
history(),
|
||||
lineNumbers(),
|
||||
highlightActiveLine(),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
indentOnInput(),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
editorHighlighting,
|
||||
themeCompartment.of(pickThemeForMatchMedia(mm)),
|
||||
langCompartment.of(lang ? [lang] : []),
|
||||
autocompleteCompartment.of(vocab ? [autocompleteExtension(vocab)] : []),
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...historyKeymap,
|
||||
...completionKeymap,
|
||||
indentWithTab,
|
||||
]),
|
||||
];
|
||||
|
||||
const state = EditorState.create({ doc: textarea.value, extensions });
|
||||
const view = new EditorView({ state, parent: textarea.parentElement });
|
||||
|
||||
// Insert the editor right before the textarea, then hide the textarea.
|
||||
textarea.parentElement.insertBefore(view.dom, textarea);
|
||||
textarea.style.display = "none";
|
||||
|
||||
// OS-level theme swap
|
||||
const onThemeChange = () => view.dispatch({
|
||||
effects: themeCompartment.reconfigure(pickThemeForMatchMedia(mm)),
|
||||
});
|
||||
mm.addEventListener("change", onThemeChange);
|
||||
|
||||
const controller = {
|
||||
getValue: () => view.state.doc.toString(),
|
||||
setContent: (text) => {
|
||||
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: text } });
|
||||
},
|
||||
setLanguage: (name) => {
|
||||
const next = pickLanguage(name);
|
||||
view.dispatch({ effects: langCompartment.reconfigure(next ? [next] : []) });
|
||||
},
|
||||
destroy: () => {
|
||||
mm.removeEventListener("change", onThemeChange);
|
||||
view.destroy();
|
||||
textarea.style.display = "";
|
||||
},
|
||||
};
|
||||
return controller;
|
||||
}
|
||||
|
||||
window.__editor = { mount };
|
||||
|
|
|
|||
Loading…
Reference in a new issue