101 lines
2.9 KiB
TypeScript
Raw Normal View History

2025-05-10 20:20:38 +03:00
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
import { EditorView, highlightActiveLine, keymap, lineNumbers, placeholder, ViewUpdate, type EditorViewConfig } from "@codemirror/view";
import { defaultHighlightStyle, StreamLanguage, syntaxHighlighting, indentUnit, bracketMatching } from "@codemirror/language";
import { Compartment } from "@codemirror/state";
import { highlightSelectionMatches } from "@codemirror/search";
import byMimeType from "./syntax_highlighting.js";
2025-05-10 20:07:53 +03:00
type ContentChangedListener = () => void;
export interface EditorConfig extends EditorViewConfig {
2025-05-11 11:27:27 +03:00
placeholder?: string;
2025-05-11 12:07:54 +03:00
lineWrapping?: boolean;
2025-05-10 20:07:53 +03:00
onContentChanged?: ContentChangedListener;
}
2025-05-10 19:10:30 +03:00
export default class CodeMirror extends EditorView {
2025-05-10 20:07:53 +03:00
private config: EditorConfig;
private languageCompartment: Compartment;
2025-05-10 20:07:53 +03:00
constructor(config: EditorConfig) {
const languageCompartment = new Compartment();
2025-05-10 20:07:53 +03:00
let extensions = [
2025-05-10 20:20:38 +03:00
keymap.of([
...defaultKeymap,
indentWithTab
]),
languageCompartment.of([]),
syntaxHighlighting(defaultHighlightStyle),
highlightActiveLine(),
highlightSelectionMatches(),
bracketMatching(),
2025-05-11 11:37:52 +03:00
lineNumbers(),
indentUnit.of(" ".repeat(4))
2025-05-10 20:07:53 +03:00
];
if (Array.isArray(config.extensions)) {
extensions = [...extensions, ...config.extensions];
}
2025-05-11 11:27:27 +03:00
if (config.placeholder) {
extensions.push(placeholder(config.placeholder));
}
2025-05-11 12:07:54 +03:00
if (config.lineWrapping) {
extensions.push(EditorView.lineWrapping);
}
2025-05-10 20:07:53 +03:00
if (config.onContentChanged) {
extensions.push(EditorView.updateListener.of((v) => this.#onDocumentUpdated(v)));
}
2025-05-10 19:10:30 +03:00
super({
...config,
2025-05-10 20:07:53 +03:00
extensions
2025-05-10 19:10:30 +03:00
});
2025-05-10 20:07:53 +03:00
this.config = config;
this.languageCompartment = languageCompartment;
2025-05-10 20:07:53 +03:00
}
#onDocumentUpdated(v: ViewUpdate) {
if (v.docChanged) {
this.config.onContentChanged?.();
}
}
getText() {
return this.state.doc.toString();
2025-05-10 19:10:30 +03:00
}
setText(content: string) {
this.dispatch({
changes: {
from: 0,
to: this.state.doc.length,
insert: content || "",
}
})
}
async setMimeType(mime: string) {
const newExtension = [];
const correspondingSyntax = byMimeType[mime];
if (correspondingSyntax) {
2025-05-11 10:54:15 +03:00
const resolvedSyntax = await correspondingSyntax();
if ("token" in resolvedSyntax) {
const extension = StreamLanguage.define(resolvedSyntax);
newExtension.push(extension);
} else {
newExtension.push(resolvedSyntax());
}
}
this.dispatch({
effects: this.languageCompartment.reconfigure(newExtension)
});
}
2025-05-10 19:10:30 +03:00
}