UNPKG

starboard-notebook

Version:

Starboard Notebook

130 lines 5.75 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { EditorView, highlightActiveLine, highlightSpecialChars, keymap } from "@codemirror/view"; import { EditorState, Compartment, Transaction } from "@codemirror/state"; import { defaultKeymap, indentMore, indentLess } from "@codemirror/commands"; import { bracketMatching } from "@codemirror/matchbrackets"; import { closeBrackets } from "@codemirror/closebrackets"; import { codeFolding, foldGutter, foldKeymap } from "@codemirror/fold"; import { lineNumbers } from "@codemirror/gutter"; import { commentKeymap } from "@codemirror/comment"; import { history, historyKeymap } from "@codemirror/history"; import { autocompletion, completionKeymap } from "@codemirror/autocomplete"; import { highlightSelectionMatches, searchKeymap } from "@codemirror/search"; import { starboardHighlighter } from "./highlightStyle"; import { getCodemirrorLanguageExtension } from "./languages"; function tabKeyRun(t) { if (t.state.selection.ranges.some((r) => !r.empty)) { return indentMore({ state: t.state, dispatch: t.dispatch }); } // So we can tab past the editor we don't tab if the cursor is at the very start of the document. // Note: this can be improved, we could instead see if we have pressed any keys since focusing the editor and decide // based on that. const r = t.state.selection.ranges[0]; if (r && r.to === 0 && r.from === 0) { return false; } t.dispatch(t.state.update(t.state.replaceSelection("\t"), { scrollIntoView: true, annotations: Transaction.userEvent.of("input"), })); return true; } // Shared between all instances const commonExtensions = [ bracketMatching(), closeBrackets(), codeFolding(), lineNumbers(), foldGutter(), highlightSpecialChars(), starboardHighlighter, highlightActiveLine(), highlightSelectionMatches(), history(), keymap.of([ { key: "Shift-Enter", run: () => true }, { key: "Alt-Enter", run: () => true }, { key: "Ctrl-Enter", run: () => true }, { key: "Tab", run: tabKeyRun }, { key: "Shift-Tab", run: indentLess }, ...defaultKeymap, ...commentKeymap, ...completionKeymap, ...historyKeymap, ...foldKeymap, ...searchKeymap, ]), autocompletion(), ]; export function createCodeMirrorEditor(element, cell, opts, runtime) { const listen = EditorView.updateListener.of((update) => { if (update.docChanged) { cell.textContent = update.state.doc.toString(); } }); const readOnlyCompartment = new Compartment(); const readOnlyExtension = EditorView.editable.of(!cell.metadata.properties.locked); const cellSwitchExtension = keymap.of([ { key: "ArrowUp", run: (target) => { if (target.state.selection.ranges.length === 1 && target.state.selection.ranges[0].empty) { const firstLine = target.state.doc.line(1); const cursorPosition = target.state.selection.ranges[0].head; if (firstLine.from <= cursorPosition && cursorPosition <= firstLine.to) { runtime.controls.focusCell({ id: cell.id, focusTarget: "previous" }); return true; } } return false; }, }, { key: "ArrowDown", run: (target) => { if (target.state.selection.ranges.length === 1 && target.state.selection.ranges[0].empty) { const lastline = target.state.doc.line(target.state.doc.lines); const cursorPosition = target.state.selection.ranges[0].head; if (lastline.from <= cursorPosition && cursorPosition <= lastline.to) { runtime.controls.focusCell({ id: cell.id, focusTarget: "next" }); return true; } } return false; }, }, ]); const languageExtension = getCodemirrorLanguageExtension(opts.language); const editorView = new EditorView({ state: EditorState.create({ doc: cell.textContent.length === 0 ? undefined : cell.textContent, extensions: [ cellSwitchExtension, ...commonExtensions, ...(languageExtension ? [languageExtension] : []), ...(opts.wordWrap === "on" ? [EditorView.lineWrapping] : []), readOnlyCompartment.of(readOnlyExtension), listen, ], }), }); const setEditable = (editor, locked) => { editor.dispatch({ effects: readOnlyCompartment.reconfigure(EditorView.editable.of(!locked)), }); }; let isLocked = cell.metadata.properties.locked; runtime.controls.subscribeToCellChanges(cell.id, () => { // Note this function will be called on ALL text changes, so any letter typed, // it's probably better for performance to only ask cm to change it's editable state if it actually changed. if (isLocked === cell.metadata.properties.locked) return; isLocked = cell.metadata.properties.locked; setEditable(editorView, isLocked); }); element.appendChild(editorView.dom); return editorView; } //# sourceMappingURL=editor.js.map