1// CodeMirror YAML editor.
2import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
3import { HighlightStyle, StreamLanguage, syntaxHighlighting } from "@codemirror/language";
4import { yaml } from "@codemirror/legacy-modes/mode/yaml";
5import { EditorState } from "@codemirror/state";
6import { EditorView, drawSelection, highlightActiveLine, keymap, lineNumbers } from "@codemirror/view";
7import { tags } from "@lezer/highlight";
9const highlight = HighlightStyle.define([
10 { tag: tags.atom, color: "#e0a23a" }, { tag: tags.number, color: "#9cdcfe" }, { tag: tags.string, color: "#ce9178" },
11 { tag: tags.comment, color: "#6a9955", fontStyle: "italic" }, { tag: tags.keyword, color: "#c586c0" }, { tag: tags.meta, color: "#9aa0a8" },
12]);
14const theme = EditorView.theme({
15 "&": { backgroundColor: "#111318", color: "#e6e6e6" },
16 ".cm-gutters": { backgroundColor: "#15181d", color: "#5e646c", border: "none" },
17 ".cm-activeLine": { backgroundColor: "#1a1e25" },
18 ".cm-activeLineGutter": { backgroundColor: "#1a1e25" },
19 "&.cm-focused .cm-cursor": { borderLeftColor: "#e6e6e6" },
20 "&.cm-focused .cm-selectionBackground, ::selection": { backgroundColor: "#2c3a55" },
21}, { dark: true });
23export class YamlEditor {
24 readonly view: EditorView;
25 private suppress = false;
27 constructor(parent: HTMLElement, initial: string, onChange: (text: string) => void) {
28 let timer: number | undefined;
29 this.view = new EditorView({
30 parent,
31 state: EditorState.create({
32 doc: initial,
33 extensions: [
34 lineNumbers(), history(), drawSelection(), highlightActiveLine(),
35 keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
36 StreamLanguage.define(yaml), syntaxHighlighting(highlight), theme,
37 EditorView.updateListener.of((u) => {
38 if (!u.docChanged || this.suppress) return;
39 window.clearTimeout(timer);
40 timer = window.setTimeout(() => onChange(this.text), 250);
41 }),
42 ],
43 }),
44 });
45 }
47 get text(): string { return this.view.state.doc.toString(); }
49 setText(text: string) {
50 if (text === this.text) return;
51 this.suppress = true;
52 this.view.dispatch({ changes: { from: 0, to: this.view.state.doc.length, insert: text } });
53 this.suppress = false;
54 }
55}