1 import {MarkdownEditorInput, MarkdownEditorInputSelection} from "./interface";
2 import {MarkdownEditor} from "../index.mjs";
3 import {EditorView} from "@codemirror/view";
4 import {ChangeSpec, SelectionRange, TransactionSpec} from "@codemirror/state";
7 export class CodemirrorInput implements MarkdownEditorInput {
9 protected editor: MarkdownEditor;
10 protected cm: EditorView;
12 constructor(cm: EditorView) {
17 if (!this.editor.cm.hasFocus) {
18 this.editor.cm.focus();
22 getSelection(): MarkdownEditorInputSelection {
23 return this.editor.cm.state.selection.main;
26 getSelectionText(selection: MarkdownEditorInputSelection|null = null): string {
27 selection = selection || this.getSelection();
28 return this.editor.cm.state.sliceDoc(selection.from, selection.to);
31 setSelection(selection: MarkdownEditorInputSelection, scrollIntoView: boolean = false) {
32 this.editor.cm.dispatch({
33 selection: {anchor: selection.from, head: selection.to},
39 return this.editor.cm.state.doc.toString();
42 getTextAboveView(): string {
43 const blockInfo = this.editor.cm.lineBlockAtHeight(scrollEl.scrollTop);
44 return this.editor.cm.state.sliceDoc(0, blockInfo.from);
47 setText(text: string, selection: MarkdownEditorInputSelection | null = null) {
48 selection = selection || this.getSelection();
49 const newDoc = this.editor.cm.state.toText(text);
50 const newSelectFrom = Math.min(selection.from, newDoc.length);
51 const scrollTop = this.editor.cm.scrollDOM.scrollTop;
52 this.dispatchChange(0, this.editor.cm.state.doc.length, text, newSelectFrom);
54 window.requestAnimationFrame(() => {
55 this.editor.cm.scrollDOM.scrollTop = scrollTop;
59 spliceText(from: number, to: number, newText: string, selection: MarkdownEditorInputSelection | null = null) {
60 const end = (selection?.from === selection?.to) ? null : selection?.to;
61 this.dispatchChange(from, to, newText, selection?.from, end)
64 appendText(text: string) {
65 const end = this.editor.cm.state.doc.length;
66 this.dispatchChange(end, end, `\n${text}`);
69 getLineText(lineIndex: number = -1): string {
70 const index = lineIndex > -1 ? lineIndex : this.getSelection().from;
71 return this.editor.cm.state.doc.lineAt(index).text;
74 wrapLine(start: string, end: string) {
75 const selectionRange = this.getSelection();
76 const line = this.editor.cm.state.doc.lineAt(selectionRange.from);
77 const lineContent = line.text;
81 if (lineContent.startsWith(start) && lineContent.endsWith(end)) {
82 newLineContent = lineContent.slice(start.length, lineContent.length - end.length);
83 lineOffset = -(start.length);
85 newLineContent = `${start}${lineContent}${end}`;
86 lineOffset = start.length;
89 this.dispatchChange(line.from, line.to, newLineContent, selectionRange.from + lineOffset);
92 coordsToSelection(x: number, y: number): MarkdownEditorInputSelection {
93 const cursorPos = this.editor.cm.posAtCoords({x, y}, false);
94 return {from: cursorPos, to: cursorPos};
98 * Dispatch changes to the editor.
100 protected dispatchChange(from: number, to: number|null = null, text: string|null = null, selectFrom: number|null = null, selectTo: number|null = null): void {
101 const change: ChangeSpec = {from};
106 change.insert = text;
108 const tr: TransactionSpec = {changes: change};
111 tr.selection = {anchor: selectFrom};
113 tr.selection.head = selectTo;
117 this.cm.dispatch(tr);