import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
import { markdown as markdownLanguage } from "@codemirror/lang-markdown";
import { EditorSelection, EditorState } from "@codemirror/state";
import {
    drawSelection,
    EditorView,
    highlightActiveLine,
    highlightSpecialChars,
    keymap,
    lineNumbers,
} from "@codemirror/view";
import { sourceEditorTheme, sourceMarkdownHighlightExtensions } from "./themes";

export class SourceMarkdownEditor {
    private readonly view: EditorView;

    public constructor(parent: HTMLElement, value: string, onChange: (value: string) => void) {
        const state = EditorState.create({
            doc: value,
            extensions: [
                lineNumbers(),
                highlightSpecialChars(),
                history(),
                drawSelection(),
                highlightActiveLine(),
                ...sourceMarkdownHighlightExtensions,
                markdownLanguage(),
                EditorView.lineWrapping,
                keymap.of([indentWithTab, ...defaultKeymap, ...historyKeymap]),
                sourceEditorTheme,
                EditorView.updateListener.of((update) => {
                    if (update.docChanged) {
                        onChange(update.state.doc.toString());
                    }
                }),
            ],
        });

        this.view = new EditorView({ state, parent });
    }

    public getValue(): string {
        return this.view.state.doc.toString();
    }

    public setValue(value: string): void {
        if (value === this.getValue()) {
            return;
        }

        this.view.dispatch({
            changes: { from: 0, to: this.view.state.doc.length, insert: value },
        });
    }

    public insertValue(value: string): void {
        const changes = this.view.state.changeByRange((range) => ({
            changes: { from: range.from, to: range.to, insert: value },
            range: EditorSelection.cursor(range.from + value.length),
        }));

        this.view.dispatch(changes);
        this.view.focus();
    }

    public reveal(): void {
        this.view.requestMeasure();
        this.view.focus();
    }

    public destroy(): void {
        this.view.destroy();
    }
}
评论加载中...