Skip to content

Syntax and highlighting

VS Code language extensions often start with a TextMate grammar. Lapis uses CodeMirror 6 with Lezer parsers instead. Syntax highlighting, bracket behavior, and basic editing affordances come from language packages and editor extensions.

The usual pattern combines extension routing with a CodeMirror language package:

import { javascript } from "@codemirror/lang-javascript";
import { markupEditor } from "@lapis-notes/api/editor";
this.registerExtensions(["ts"], "typescript");
this.registerEditorExtension(
markupEditor(javascript({ typescript: true })),
"typescript",
);

The bundled LangCode plugin registers JavaScript, TypeScript, JSON, YAML, CSS, and plain text this way using packages from @codemirror/lang-*.

Steps:

  1. Map extensions to a view type with registerExtensions()
  2. Register a file-backed view (see Editor views and associations)
  3. Attach the parser with registerEditorExtension(markupEditor(languagePackage()), viewType)

Markdown files embed other languages inside fenced code blocks. The markdown plugin composes CodeMirror’s markdown parser with @codemirror/language-data and lazy-loaded extras such as Mermaid.

For your own markdown extension, follow the same idea:

  • Extend the markdown Language with codeLanguages
  • Provide a LanguageDescription for each nested language you support
  • Use lazy loading for heavy grammars

Reading-mode and preview surfaces may use highlight.js CSS for read-only token styling. Editor surfaces use the shared classHighlighter, which emits cm-* classes and compatible hljs-* classes where vocabularies overlap.

When no off-the-shelf @codemirror/lang-* package exists, define a Lezer grammar and wrap it in a CodeMirror Language.

The search plugin uses a custom query grammar in the API package. Custom grammars can also add:

  • Autocomplete tied to the grammar AST
  • Parse diagnostics for invalid input
  • Language-specific close-bracket configuration

Language configuration (brackets, indent, comments)

Section titled “Language configuration (brackets, indent, comments)”

VS Code’s language configuration file controls comments, brackets, and indentation rules. Lapis applies equivalent behavior through:

  • Shared editor defaults — bracket matching, auto-closing, indent-on-input, tab vs spaces from vault settings
  • Per-language languageData — for example custom closeBrackets on the search query language
  • CodeMirror extensions — folding, comment toggling, and language-specific input handlers registered by plugins

There is no language-configuration.json contribution point. Set behavior in the CodeMirror extension bundle you register for the view type.

Editor tokens receive CSS classes from Lezer highlight tags through the shared highlighter. Theme CSS can target:

  • cm-* classes from CodeMirror highlight tags
  • Compatible hljs-* classes when a highlight.js theme is already loaded

Plugin CSS should follow the plugin CSS contract and prefer stable token classes over fragile DOM structure selectors.

The markdown plugin is the reference implementation for rich syntax in Lapis. It registers many CodeMirror extensions, including:

Extension areaExamples
StructureTables, grid tables, headings, lists, blockquotes
Vault syntaxWiki links, tags, embed links
DirectivesContainer directives, notebook cells
Rich editingLive preview decorations and widgets
Nested codeInner-language highlighting inside fenced blocks

Study the markdown plugin when you need live-preview decorations, custom Lezer nodes, or vault-aware autocomplete inside an editor.

  • TextMate grammars (.tmLanguage, .json grammars) — not supported in the editing pipeline
  • Direct Monaco APIs — the workspace editor is CodeMirror-based
  • VS Code semantic token providers — use Lezer highlight tags and language-service diagnostics instead