UNPKG

@jinntec/jinn-codemirror

Version:

Source code editor component based on codemirror with language support for XML and Leiden+

137 lines (132 loc) 3.77 kB
import { EditorSelection, StateEffect, StateField } from "@codemirror/state"; import { EditorView, showPanel } from "@codemirror/view"; import { syntaxTree } from "@codemirror/language"; import { wrapCommand, snippetCommand } from "./config"; const toggleEncloseWith = StateEffect.define(); const encloseWithState = StateField.define({ create: () => false, update(value, tr) { for (let e of tr.effects) { if (e.is(toggleEncloseWith )) { value = e.value; } } return value; }, provide: (f) => showPanel.from(f, (on) => on ? createEncloseWithPanel : null) }); function createEncloseWithPanel(view) { const dom = document.createElement("div"); dom.className = "cm-input-panel"; const input = document.createElement("input"); input.type = "text"; input.placeholder = "Name of element (enter to confirm, esc to abort)"; input.addEventListener("keyup", (ev) => { switch (ev.key) { case "Enter": ev.preventDefault(); const tag = input.value; if (tag !== "") { wrapCommand(`<${tag}>`, `</${tag}>`)(view); view.focus(); } case "Esc": case "Escape": view.dispatch({ effects: toggleEncloseWith .of(false) }); } }); dom.appendChild(input); return { top: false, dom, mount: () => setTimeout(() => input.focus(), 50) }; } const encloseWithTheme = EditorView.baseTheme({ ".cm-input-panel": { padding: "5px 10px", fontFamily: "monospace" }, ".cm-input-panel input": { width: "100%" } }); function encloseWithPanel() { return [encloseWithState, encloseWithTheme]; } const encloseWithCommand = (editor) => { editor.dispatch({ effects: toggleEncloseWith .of(!editor.state.field(encloseWithState)) }); return true; }; const selectElementCommand = (editor) => { editor.dispatch(editor.state.changeByRange((range) => { const at = syntaxTree(editor.state).resolveInner(range.from); let inTag = null; for (let cur = at; !inTag && cur.parent; cur = cur.parent) { if (cur.name == "Element") { inTag = cur; } } if (inTag) { return { selection: EditorSelection.range(inTag.from, inTag.to), range: EditorSelection.range(inTag.from, inTag.to) }; } return { range }; })); return true; }; const removeEnclosingCommand = (editor) => { editor.dispatch(editor.state.changeByRange((range) => { const at = syntaxTree(editor.state).resolveInner(range.from); let inTag = null; for (let cur = at; !inTag && cur.parent; cur = cur.parent) { if (cur.name == "Element") { inTag = cur; } } if (inTag) { const startTag = inTag.firstChild; const endTag = inTag.lastChild; if (startTag && endTag) { if (startTag.name === "SelfClosingTag") { return { range: EditorSelection.range(startTag.from, startTag.from), changes: [ { from: startTag.from, to: startTag.to, insert: "" } ] }; } else { return { range: EditorSelection.range(startTag.from, endTag.from - (startTag.to - startTag.from)), changes: [ { from: startTag.from, to: startTag.to, insert: "" }, { from: endTag.from, to: endTag.to, insert: "" } ] }; } } } return { range }; })); return true; }; const commands = { selectElement: selectElementCommand, removeEnclosing: removeEnclosingCommand, encloseWith: encloseWithCommand, snippet: { create: (template) => snippetCommand(template) }, }; export { commands, encloseWithCommand, encloseWithPanel, removeEnclosingCommand, selectElementCommand };