import { Compartment, EditorState } from '@codemirror/state'; import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter, drawSelection, rectangularSelection, dropCursor, highlightSpecialChars, highlightTrailingWhitespace, } from '@codemirror/view'; import { defaultKeymap, history, historyKeymap, indentWithTab, } from '@codemirror/commands'; import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; import { HighlightStyle, syntaxHighlighting, indentOnInput, bracketMatching, foldGutter, foldKeymap, indentUnit, StreamLanguage, } from '@codemirror/language'; import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap, } from '@codemirror/autocomplete'; import { python } from '@codemirror/lang-python'; import { markdown } from '@codemirror/lang-markdown'; import { yaml as yamlStreamParser } from '@codemirror/legacy-modes/mode/yaml'; import { tags } from '@lezer/highlight'; import { vim, Vim } from '@replit/codemirror-vim'; import { emacs } from '@replit/codemirror-emacs'; let anyEditorChangedFlag = false; export function anyEditorChanged() { return anyEditorChangedFlag; } export function resetAnyEditorChanged() { anyEditorChangedFlag = false; } const myListener = new Compartment(); const rlDefaultKeymap = [ ...closeBracketsKeymap, ...defaultKeymap, ...searchKeymap, ...historyKeymap, ...completionKeymap, ...foldKeymap, indentWithTab, ]; // Based on https://discuss.codemirror.net/t/dynamic-light-mode-dark-mode-how/4709/5 // Use a class highlight style, so we can handle things in CSS. const highlightStyle = HighlightStyle.define([ { tag: tags.atom, class: 'cmt-atom' }, { tag: tags.comment, class: 'cmt-comment' }, { tag: tags.keyword, class: 'cmt-keyword' }, { tag: tags.literal, class: 'cmt-literal' }, { tag: tags.number, class: 'cmt-number' }, { tag: tags.operator, class: 'cmt-operator' }, { tag: tags.separator, class: 'cmt-separator' }, { tag: tags.string, class: 'cmt-string' }, ]); const yaml = () => StreamLanguage.define(yamlStreamParser); const defaultExtensionsBase = [ lineNumbers(), history(), foldGutter(), indentOnInput(), drawSelection(), EditorState.allowMultipleSelections.of(true), dropCursor(), syntaxHighlighting(highlightStyle, { fallback: true }), bracketMatching(), closeBrackets(), autocompletion(), rectangularSelection(), highlightActiveLine(), highlightActiveLineGutter(), highlightSelectionMatches(), highlightSpecialChars(), highlightTrailingWhitespace(), ]; // based on https://codemirror.net/docs/migration/ export function editorFromTextArea(textarea, extensions, autofocus, additionalKeys) { // vim/emacs must come before other extensions extensions.push( ...defaultExtensionsBase, keymap.of([ ...rlDefaultKeymap, ...additionalKeys, ]), EditorView.updateListener.of((viewUpdate) => { if (viewUpdate.docChanged) { anyEditorChangedFlag = true; } }), myListener.of(EditorView.updateListener.of( () => { }, )), ); if (textarea.disabled || textarea.readOnly) { extensions.push( EditorState.readOnly.of(true), EditorView.editable.of(false), ); } const view = new EditorView({ doc: textarea.value, extensions }); textarea.parentNode.insertBefore(view.dom, textarea); // eslint-disable-next-line no-param-reassign textarea.style.display = 'none'; if (textarea.form) { textarea.form.addEventListener('submit', () => { // eslint-disable-next-line no-param-reassign textarea.value = view.state.doc.toString(); }); } textarea.classList.add('rl-managed-by-codemirror'); if (autofocus) { document.addEventListener('DOMContentLoaded', () => { view.focus(); }); } return view; } export function setListener(view, fn) { view.dispatch({ effects: myListener.reconfigure( EditorView.updateListener.of(fn), ), }); } Vim.defineEx('write', 'w', (cm) => { const form = cm.cm6.dom.closest('form'); if (form) { // prefer 'submit' over 'save' on flow pages let submitButton = form.querySelector("input[type='submit'][name='submit']"); if (submitButton) { anyEditorChangedFlag = false; submitButton.click(); return; } submitButton = form.querySelector("input[type='submit']"); if (submitButton) { anyEditorChangedFlag = false; submitButton.click(); } } }); export { EditorState, EditorView, indentUnit, vim, emacs, python, markdown, yaml, };