/* eslint-disable import/prefer-default-export */ // This contains the CodeMirror instance, which needs to be built into a bundle // using `npm run buildInjectedJs`. This bundle is then loaded from // NoteEditor.tsx into the webview. // // In general, since this file is harder to debug due to the intermediate built // step, it's better to keep it as light as possible - it shoud just be a light // wrapper to access CodeMirror functionalities. Anything else should be done // from NoteEditor.tsx. import createTheme from './theme'; import decoratorExtension from './decoratorExtension'; import { EditorState } from '@codemirror/state'; import { markdown } from '@codemirror/lang-markdown'; import { highlightSelectionMatches, search } from '@codemirror/search'; import { EditorView, drawSelection, highlightSpecialChars, ViewUpdate } from '@codemirror/view'; import { undo, redo, history, undoDepth, redoDepth } from '@codemirror/commands'; import { keymap } from '@codemirror/view'; import { indentOnInput } from '@codemirror/language'; import { searchKeymap } from '@codemirror/search'; import { historyKeymap, defaultKeymap } from '@codemirror/commands'; import { MarkdownMathExtension } from './markdownMathParser'; import { GFM as GitHubFlavoredMarkdownExtension } from '@lezer/markdown'; import syntaxHighlightingLanguages from './syntaxHighlightingLanguages'; interface CodeMirrorResult { editor: EditorView; undo: Function; redo: Function; select(anchor: number, head: number): void; scrollSelectionIntoView(): void; insertText(text: string): void; } function postMessage(name: string, data: any) { (window as any).ReactNativeWebView.postMessage(JSON.stringify({ data, name, })); } function logMessage(...msg: any[]) { postMessage('onLog', { value: msg }); } export function initCodeMirror(parentElement: any, initialText: string, theme: any): CodeMirrorResult { logMessage('Initializing CodeMirror...'); let schedulePostUndoRedoDepthChangeId_: any = 0; function schedulePostUndoRedoDepthChange(editor: EditorView, doItNow: boolean = false) { if (schedulePostUndoRedoDepthChangeId_) { if (doItNow) { clearTimeout(schedulePostUndoRedoDepthChangeId_); } else { return; } } schedulePostUndoRedoDepthChangeId_ = setTimeout(() => { schedulePostUndoRedoDepthChangeId_ = null; postMessage('onUndoRedoDepthChange', { undoDepth: undoDepth(editor.state), redoDepth: redoDepth(editor.state), }); }, doItNow ? 0 : 1000); } const editor = new EditorView({ state: EditorState.create({ // See https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts // for a sample configuration. extensions: [ markdown({ extensions: [ MarkdownMathExtension, GitHubFlavoredMarkdownExtension, ], codeLanguages: syntaxHighlightingLanguages, }), ...createTheme(theme), history(), search(), drawSelection(), highlightSpecialChars(), highlightSelectionMatches(), indentOnInput(), decoratorExtension, EditorView.lineWrapping, EditorView.contentAttributes.of({ autocapitalize: 'sentence' }), EditorView.updateListener.of((viewUpdate: ViewUpdate) => { if (viewUpdate.docChanged) { postMessage('onChange', { value: editor.state.doc.toString() }); schedulePostUndoRedoDepthChange(editor); } if (!viewUpdate.state.selection.eq(viewUpdate.startState.selection)) { const mainRange = viewUpdate.state.selection.main; const selStart = mainRange.from; const selEnd = mainRange.to; postMessage('onSelectionChange', { selection: { start: selStart, end: selEnd } }); } }), keymap.of([ ...defaultKeymap, ...historyKeymap, ...searchKeymap, ]), ], doc: initialText, }), parent: parentElement, }); return { editor, undo: () => { undo(editor); schedulePostUndoRedoDepthChange(editor, true); }, redo: () => { redo(editor); schedulePostUndoRedoDepthChange(editor, true); }, select: (anchor: number, head: number) => { editor.dispatch(editor.state.update({ selection: { anchor, head }, scrollIntoView: true, })); }, scrollSelectionIntoView: () => { editor.dispatch(editor.state.update({ scrollIntoView: true, })); }, insertText: (text: string) => { editor.dispatch(editor.state.replaceSelection(text)); }, }; }