feat(editor): Mermaid syntax highlighting, linter and autocomplete

- StreamLanguage tokenizer for Mermaid: diagram-type keywords, general
  keywords, directions, arrows/links, strings, %% comments, init
  directives and YAML frontmatter, with a dark-theme HighlightStyle
- live linter via mermaid.parse: maps parse errors to the right editor
  line (accounting for the frontmatter lines mermaid strips), with wavy
  underline, gutter marker and message tooltip
- autocomplete: Mermaid keyword/type/direction options plus starter
  diagram snippets offered on the first line; bracket closing
- dark-themed completion + diagnostic tooltips

Adds @codemirror/lint, @codemirror/autocomplete and @lezer/highlight.
This commit is contained in:
2026-05-22 16:46:43 +03:00
parent 29bf6438b3
commit b648ee904d
4 changed files with 414 additions and 1 deletions

View File

@@ -17,7 +17,10 @@
indentWithTab,
} from '@codemirror/commands';
import { bracketMatching, indentOnInput } from '@codemirror/language';
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
import { lintKeymap } from '@codemirror/lint';
import { store } from '../store.svelte';
import { mermaidSupport } from '../cm-mermaid';
let host: HTMLDivElement;
let view: EditorView | undefined;
@@ -48,6 +51,43 @@
backgroundColor: 'var(--accent-soft) !important',
},
'.cm-matchingBracket': { backgroundColor: 'var(--accent-soft)', outline: 'none' },
// Autocomplete popup
'.cm-tooltip': {
backgroundColor: 'var(--bg-elev-2)',
border: '1px solid var(--border-strong)',
borderRadius: '6px',
color: 'var(--text)',
boxShadow: '0 8px 28px rgba(0,0,0,0.45)',
},
'.cm-tooltip.cm-tooltip-autocomplete > ul': { fontFamily: 'var(--mono)', maxHeight: '16em' },
'.cm-tooltip-autocomplete ul li[aria-selected]': {
backgroundColor: 'var(--accent)',
color: '#fff',
},
'.cm-completionIcon': { color: 'var(--text-dim)', paddingRight: '0.6em' },
'.cm-completionDetail': { color: 'var(--text-faint)', fontStyle: 'normal' },
'.cm-completionInfo': {
backgroundColor: 'var(--bg-elev-2)',
border: '1px solid var(--border-strong)',
borderRadius: '6px',
color: 'var(--text-dim)',
},
// Lint diagnostics
'.cm-diagnostic': {
fontFamily: 'var(--mono)',
fontSize: '12px',
borderLeft: '3px solid var(--red)',
padding: '6px 8px',
},
'.cm-diagnostic-error': { borderLeftColor: 'var(--red)' },
'.cm-lintRange-error': {
backgroundImage: 'none',
textDecoration: 'underline wavy var(--red)',
textDecorationSkipInk: 'none',
},
'.cm-lint-marker-error': { content: '""' },
},
{ dark: true }
);
@@ -65,8 +105,16 @@
drawSelection(),
indentOnInput(),
bracketMatching(),
closeBrackets(),
highlightActiveLine(),
keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
...mermaidSupport(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...historyKeymap,
...lintKeymap,
indentWithTab,
]),
theme,
EditorView.lineWrapping,
EditorView.updateListener.of((u) => {