Language Server
Panache includes a built-in language server protocol (LSP) implementation that provides a rich set of editor features for Pandoc Markdown, Quarto, and R Markdown files.
Panache includes a built-in language server protocol (LSP) implementation that provides a rich set of editor features for Pandoc Markdown, Quarto, and R Markdown files. The language server is designed to be editor-agnostic and configurations for popular editors like Neovim, VS Code, Helix, Zed, and Emacs are provided in Editor Configuration.
Features
In this section, we provide an overview of the key features supported by the Panache LSP.
- Document formatting
-
Format entire documents or selected ranges (
textDocument/formatting,textDocument/rangeFormatting) - On-type indentation
-
Indent the new line to the list item’s content column after you press Enter inside a list (
textDocument/onTypeFormatting) - Go to definition
-
Jump from references to definitions (
textDocument/definition)- Reference links:
[text][ref]→[ref]: url - Reference images:
![alt][ref]→[ref]: url - Footnote references:
[^id]→[^id]: content - Citation references:
[@key]→[@key]: content - Quarto crossrefs:
@fig-label→ chunk/equation/figure label definition
- Reference links:
- Find references
-
Find all usages of a symbol (
textDocument/references)- Quarto crossrefs and chunk labels
- Citation keys (with optional bibliography/inline-reference declarations)
- Document highlight
-
Highlight every occurrence of the symbol under the cursor (
textDocument/documentHighlight) - Document outline
-
Hierarchical view of document structure (
textDocument/documentSymbol)- Headings (H1-H6) with proper nesting
- Tables (with captions when available)
- Figures (images with alt text)
- YAML frontmatter (with a document-shape summary)
- Workspace symbols
-
Search headings across all open documents and the project graph (
workspace/symbol) - Document links
-
Clickable links, images, autolinks, and
{{< include >}}shortcode paths (textDocument/documentLink,documentLink/resolve) - Code folding
-
Fold sections of your document (
textDocument/foldingRange)- Headings
- Code blocks
- Fenced divs
- YAML frontmatter
- Semantic tokens
-
Additive, flavor-gated highlighting for Pandoc/Quarto-specific syntax the editor’s base grammar misses (
textDocument/semanticTokens/full)- Citations and Quarto cross-references
- Shortcodes, fenced-div info strings, and bracketed-span attributes
- Math delimiters and footnote references
Layered on top of your editor’s highlighting via a custom legend; opt in by mapping the token types to colors (see Semantic Tokens below).
- Diagnostics
-
Real-time linting as you type (
textDocument/publishDiagnostics)- Heading hierarchy violations
- Duplicate references
- Citation validation
- Parser errors
- External linter integration (e.g., R code linting)
Built-in diagnostics are debounced: a burst of keystrokes coalesces into a single lint pass once you pause briefly, so typing stays responsive on large documents. External linters (e.g.
jarl,ruff) are more expensive—they spawn a subprocess per embedded code block—so they run on save rather than on every keystroke. Save your file to refresh external-linter diagnostics.Both the push (
textDocument/publishDiagnostics) and pull (textDocument/diagnostic,workspace/diagnostic) models are supported; the server chooses per client (see Push vs. pull diagnostics below). - Code actions
-
Quick fixes and refactorings (
textDocument/codeAction)- Auto-fix lint issues, individually or via “Fix all auto-fixable”
- Convert list spacing (loose ↔︎ compact)
- Convert list markers (bullet ↔︎ ordered ↔︎ task)
- Convert footnote styles (inline ↔︎ reference)
- Convert link styles (inline ↔︎ reference)
- Convert implicit heading references to explicit links
- Hover information
-
Contextual information on hover (
textDocument/hover)- Footnote definitions
- Citation previews (formatted bibliography entry)
- Section previews for heading links, reference definitions, and crossrefs
- Equation previews for equation crossrefs
- Linked-document metadata (title and first paragraph)
- Auto-completion
-
Smart completions for Markdown syntax (
textDocument/completion)- Citation keys, with a formatted bibliography preview attached lazily via
completionItem/resolvewhen an item is focused - Reference labels
- File paths inside
[](...)anddestinations - File paths inside Quarto shortcodes (
include,embed,video,placeholder)
- Citation keys, with a formatted bibliography preview attached lazily via
- Symbol renaming
-
Rename references and their definitions together (
textDocument/rename,textDocument/prepareRename)- Citation keys
- Reference labels
- Footnote labels
- Quarto crossref labels (including executable chunk
labeloptions)
- Linked editing
-
Edit a symbol and its linked occurrences in the current document together as you type (
textDocument/linkedEditingRange). Editors that support it (e.g. VS Code’s “Editor: Linked Editing”) update every linked span live, without invoking a rename.- Reference labels and their definitions (
[text][label]↔︎[label]: …) - Footnote labels (
[^id]↔︎[^id]: …) - Citation keys, Quarto crossrefs, and chunk labels
- Heading ids and the explicit links that point at them
- Reference labels and their definitions (
- File rename
-
Update links, shortcode paths, and frontmatter/config file paths pointing at a renamed file (
workspace/willRenameFiles) - File operations
-
Refresh cross-document diagnostics after a file is created, renamed, or deleted (
workspace/didCreateFiles,didRenameFiles,didDeleteFiles) - Document tracking
-
Incremental synchronization for efficiency (
textDocument/didOpen,textDocument/didChange,textDocument/didClose) - Configuration discovery
- Automatic detection from workspace root
- Live configuration reload
-
Re-read
panache.toml/.panache.tomland apply client settings without a restart when the editor sendsworkspace/didChangeConfigurationor when a config file changes on disk (workspace/didChangeWatchedFiles). All open documents are re-linted with the new configuration. - Referenced-file updates
-
Pick up changes to referenced files (bibliographies, includes, project manifests) when the editor reports them via
workspace/didChangeWatchedFiles. As a fallback for clients whose file-watching is incomplete—notably, Neovim does not emit a watch event for a bibliography that is open in a buffer—the server also re-reads referenced files from disk on the next document activity, so an out-of-band edit is reflected without reloading the document. Files you have open in the editor are never re-read this way; their in-editor content takes precedence. - Config error reporting
-
When a discovered
panache.tomlfails to parse (e.g. an unknown key), the server does not silently fall back to default formatting. It publishes an error diagnostic on the config file itself (anchored at the offending key, shown even when the file isn’t open), raises a one-timewindow/showMessagenotification, and refuses to format the affected documents until the config parses again. All three clear automatically once the config is fixed. See Validation for the underlying rules.
Editor Configuration
If you want to start the language server manually (for debugging), then you can run panache lsp in your terminal. This will start the server and wait for JSON-RPC input. But most users will want to set up their editor to start the server automatically when editing supported files. See the editor integration instructions below.
Neovim
Neovim does not recognize the mdsvex extensions (.svx, .svelte.md) out of the box, so map them to a filetype first (skip this if you only edit .qmd/.md/.rmd):
vim.filetype.add({
extension = { svx = "mdsvex" },
pattern = { [".*%.svelte%.md"] = "mdsvex" },
})In Neovim +0.11, you can use the built-in LSP client:
-- .config/nvim/lsp/panache.lua
return {
cmd = { "panache", "lsp" },
filetypes = { "quarto", "markdown", "rmarkdown", "mdsvex" },
root_markers = { ".panache.toml", "panache.toml", ".git" },
settings = {},
}
-- Enable it
vim.lsp.enable({"panache"})For earlier Neovim releases, use nvim-lspconfig:
-- Add to your LSP config
local lspconfig = require("lspconfig")
local configs = require("lspconfig.configs")
-- Define panache LSP
if not configs.panache then
configs.panache = {
default_config = {
cmd = { "panache", "lsp" },
filetypes = { "quarto", "markdown", "rmarkdown", "mdsvex" },
root_dir = lspconfig.util.root_pattern(
".panache.toml",
"panache.toml",
".git"
),
settings = {},
},
}
end
-- Enable it
lspconfig.panache.setup({})Note that you need to have panache in your PATH for the above configurations to work.
To format on save, add an autocmd:
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = { "*.qmd", "*.md", "*.rmd", "*.svx", "*.svelte.md" },
callback = function()
vim.lsp.buf.format({ async = false })
end,
})Format the current buffer with:
:lua vim.lsp.buf.format()
Or map it to a key:
vim.keymap.set("n", "<leader>f", vim.lsp.buf.format, { desc = "Format buffer" })VS Code
Install the VS Code Marketplace extension. The extension starts the language server automatically for supported files.
To install through the command line, run:
code --install-extension jolars.panacheIf you prefer to install from the VS Code UI, launch VS Code Quick Open (Ctrl+P), then run:
ext install jolars.panacheThe extension launches panache lsp automatically.
Optional extension settings:
{
"panache.commandPath": "panache",
"panache.downloadBinary": true,
"panache.releaseTag": "latest",
"panache.serverArgs": [],
"panache.serverEnv": { "RUST_LOG": "info" },
"panache.trace.server": "off",
"panache.experimental.incrementalParsing": false
}panache.experimental.incrementalParsing enables an experimental incremental parse path for textDocument/didChange. It is disabled by default.
Open VSX (Positron, Cursor, VSCodium, etc.)
Install the Open VSX extension. The extension starts the language server automatically for supported files.
The Open VSX extension is identical to the VS Code extension and uses the same configuration settings, so see the VS Code section above for configuration instructions.
Helix
Add to ~/.config/helix/languages.toml:
[[language]]
name = "markdown"
language-servers = ["panache"]
auto-format = true
[[language]]
name = "quarto"
language-servers = ["panache"]
auto-format = true
[[language]]
name = "rmarkdown"
language-servers = ["panache"]
auto-format = true
[language-server.panache]
command = "panache"
args = ["lsp"]Format the current file with :format or <space>f.
Emacs
Using lsp-mode:
(require 'lsp-mode)
;; Define panache LSP client
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection '("panache" "lsp"))
:activation-fn (lsp-activate-on "quarto" "markdown" "rmarkdown")
:server-id 'panache))
;; Enable for specific modes
(add-hook 'markdown-mode-hook #'lsp-deferred)
(add-hook 'quarto-mode-hook #'lsp-deferred)
;; Format on save
(add-hook 'before-save-hook #'lsp-format-buffer nil t)
Sublime Text
Using LSP package:
- Install LSP package
- Add to LSP settings:
{
"clients": {
"panache": {
"enabled": true,
"command": ["panache", "lsp"],
"selector": "text.html.markdown | source.quarto"
}
}
}Kate
Kate supports LSP servers via its LSP client plugin:
- Enable the LSP Client plugin
- Add to LSP client configuration:
{
"servers": {
"markdown": {
"command": ["panache", "lsp"],
"highlightingModeRegex": "^Markdown$"
}
}
}Configuration Discovery
The LSP automatically discovers configuration files from your workspace:
- Searches for
.panache.tomlorpanache.tomlfrom workspace root - Falls back to
~/.config/panache/config.toml - Uses built-in defaults if no config found
Workspace Root Detection
The LSP determines the workspace root by looking for:
.panache.tomlorpanache.toml.gitdirectory- Project-specific files (
.quarto.yml,_quarto.yml, etc.)
Multi-Root Workspaces
The LSP supports workspaces with multiple root folders. Each document resolves its configuration against the folder that contains it (the closest-matching workspace folder), so two folders with different panache.toml files each get their own settings. Adding or removing folders at runtime (workspace/didChangeWorkspaceFolders) re-resolves the configuration of every open document live, without a restart.
Capabilities
Document Formatting
The LSP provides full document and range formatting via textDocument/formatting and textDocument/rangeFormatting requests. This uses the same formatting engine as the panache format CLI command.
Format entire documents or selected ranges:
" Neovim: format buffer
:lua vim.lsp.buf.format()
" Neovim: format selected range (visual mode)
:'<,'>lua vim.lsp.buf.format()
Format on save is supported by all major editors. See editor configuration sections above for setup instructions.
The LSP honors the exclude and extend-exclude patterns from the discovered panache.toml for whole-document formatting (textDocument/formatting): files whose path (relative to the project anchor) matches an exclude pattern are skipped, and the server returns no edits. This means format-on-save will leave vendored, generated, or otherwise opted-out documents alone—matching what panache format does when it walks a directory.
Range formatting (textDocument/rangeFormatting) intentionally bypasses excludes. A range request only happens when you explicitly select text and invoke “format selection”, so it’s treated as the LSP equivalent of the CLI’s “explicit file target bypasses excludes” rule. If you need to format an excluded file in its entirety, select the whole buffer first or temporarily remove the exclude pattern.
On-Type Indentation
When you press Enter inside a list item, the server can indent the new line to that item’s content column via textDocument/onTypeFormatting (trigger character \n). This uses Panache’s parsed model of the list, so nested, ordered, and fancy (alphabetic/roman) lists align correctly where regex-based editor heuristics often guess wrong.
The handler is deliberately narrow: it only adjusts leading whitespace. It never inserts a list marker and never tries to decide whether you meant to start a new item or leave the list—so typing your own marker afterward stays entirely up to you. The indentation it produces matches where the formatter would place a continuation line, so on-type help and a later format pass never fight.
The request is only useful if your editor fires it. VS Code, Zed, and several others trigger onTypeFormatting out of the box; Neovim’s built-in LSP client does not, so you will need an editor-side autocmd or plugin to send it.
Deferred for now: blockquote continuation, code-block indentation, and live table alignment.
Go to Definition
Jump from link and footnote references to their definitions:
- Reference links
-
[text][label]→[label]: url - Shortcut reference links
-
[label]→[label]: url - Reference images
-
![alt][label]→[label]: url - Footnote references
-
[^id]→[^id]: content - Quarto crossrefs
-
@fig-label→#| label: fig-label(or other crossref label definitions)
Place your cursor on a reference and trigger “go to definition” (F12 in many editors).
Document Outline
The LSP provides a hierarchical document outline showing:
- Headings
- H1-H6 with proper nesting levels
- Tables
- With captions when available
- Figures
- Image links with alt text
- YAML frontmatter
- Listed with a short summary of the document’s metadata shape
Tables and figures nest under their enclosing heading. The outline appears in:
- VSCode: Outline view (sidebar) or breadcrumbs
- Neovim: Telescope symbols (
:Telescope lsp_document_symbols) or Aerial plugin - Helix: Symbol picker (
:symbol-picker)
The outline updates automatically as you edit.
Code Actions
Quick fixes and refactorings available at the cursor position:
- Auto-fix lint issues
- Fix lint violations (e.g. heading hierarchy) individually, or apply a single “Fix all auto-fixable” action that batches every auto-fixable diagnostic in the document.
- Convert list spacing
- Toggle between loose (blank lines) and compact list formatting
- Convert list markers
- Switch a list between bullet, ordered, and task-list markers
- Convert footnote styles
-
Toggle between inline
^[text]and reference[^id]footnotes - Convert heading references
-
Turn an implicit heading reference into an explicit
[text](#id)link - Convert link styles
-
Toggle a single link between inline
[text](url)and reference[text][label]form. When converting to reference style, an existing definition with the same URL and title is reused; otherwise a new definition is appended (label slugged from the link text). When converting back, the definition is deleted only if the converted link was its last use.
Trigger code actions:
" Neovim: show code actions at cursor
:lua vim.lsp.buf.code_action()
Most editors show a lightbulb icon when code actions are available.
Folding Ranges
Fold sections of your document for easier navigation:
- Headings
- Fold sections under headings
- Code blocks
- Fold multi-line fenced code blocks
- Fenced divs
-
Fold
::: {.class}content - HTML blocks
-
Fold multi-line HTML blocks (e.g.
<details>,<script>,<!-- … -->,<div>) - YAML frontmatter
-
Fold
---delimited metadata
Most editors support folding with default key bindings (e.g., za in Neovim, Ctrl+Shift+[ in VSCode).
Live Diagnostics
Linting errors and warnings appear in real-time as you type:
- Built-in rules
- Heading hierarchy, duplicate references, citation validation, parser errors. Run debounced as you type.
- External linters
-
Code block linting (e.g.,
jarlfor R when configured in[linters]). Run on save, not on every keystroke, since each pass spawns a subprocess per embedded code block. - Project manifests
-
YAML parse errors in the project’s manifest files—
_quarto.yml,_metadata.yml,_bookdown.yml/_output.yml, andmetadata-files:includes—are published as diagnostics on the manifest file’s own URI (much asrust-analyzerflags a brokenCargo.toml), even when that file is not open. A malformed manifest would otherwise silently break bibliography and cross-reference resolution in your documents. The diagnostic clears automatically once the manifest is fixed and saved.
Diagnostics appear as:
- Squiggly underlines in the editor
- Hover tooltips with error messages
- Problems and diagnostics panel
Quick fixes are available via code actions where applicable.
Push vs. pull diagnostics
Panache supports both LSP diagnostic delivery models and picks one per client at initialize:
- Push (default)
-
The server sends
textDocument/publishDiagnosticsnotifications as it computes them. This is the model for clients that don’t advertise pull support. - Pull
-
Clients that advertise
textDocument.diagnosticsupport are served via the pull model instead (textDocument/diagnosticandworkspace/diagnostic), and push is suppressed for them so diagnostics aren’t reported twice. The server advertisesinter_file_dependencies(an edit can change another file’s diagnostics) andworkspace_diagnostics(whole-workspace pulls).The same lint pipeline backs both models, so pull diagnostics include the built-in rules, external linters, and unopened-manifest errors described above. Because that pipeline is asynchronous (debounced edits re-lint every open document, on-save external linters, cross-file includes), the server sends
workspace/diagnostic/refreshwhen results change so a pull client re-pulls; clients that don’t support refresh still pull on their own cadence (open, edit, focus). Repeated pulls are answered with anunchangedreport when the document’s diagnostics haven’t changed since the client’s lastresult_id.Clients that additionally advertise
relatedDocumentSupportreceive cross-file diagnostics inline: atextDocument/diagnosticpull for one document carries the diagnostics of the other files in its project graph (includes, project siblings, and manifests) underrelatedDocuments, so a duplicate label defined in an included file surfaces without waiting for a separateworkspace/diagnosticpull. Clients without that capability still receive the same cross-file diagnostics through the workspace pull.A client that supplies a
partialResultTokenon either pull receives the report incrementally: the response delivers the first chunk and the remainder streams as$/progressnotifications keyed by that token (workspace/diagnosticchunks the per-document reports;textDocument/diagnosticchunks therelatedDocumentsmap). Each report is keyed by its own URI, so the client merges the response and chunks regardless of arrival order. Without a token the whole report is returned in the response, unchanged.
Hover Information
Hover over elements to see contextual information (implementation varies by element type).
Auto-Completion
Smart completions for Markdown syntax (implementation varies by context):
- Citations and crossrefs inside
[@...]—bibliography keys, inline reference IDs, Quarto/bookdown crossref labels (e.g.fig-plot). Bibliography keys carry a formatted preview (author, year, title, journal) that is computed lazily throughcompletionItem/resolvewhen the item is focused, so large.bibfiles don’t pay the formatting cost up front. - File paths inside link and image destinations—typing inside
[text](...)orsuggests files and directories relative to the current document. Image destinations are filtered to extensions pandoc/quarto accept for: raster (.png,.jpg/.jpeg,.gif/.apng,.webp,.avif,.bmp,.tif/.tiff,.heic/.heif,.ico), vector and print (.svg,.pdf,.eps,.ps), and Quarto-embedded video (.mp4,.webm,.ogv,.mov,.m4v,.mkv,.avi,.flv,.mpeg/.mpg). Link destinations include all files. Completion auto-triggers on(and/; hidden entries (.dotfiles) appear only when the typed prefix starts with.. - Quarto shortcode paths—in
.qmddocuments, typing inside the path argument of{{< include >}},{{< embed >}},{{< video >}}, or{{< placeholder >}}lists files and directories relative to the document. Paths starting with/resolve against the LSP workspace root (Quarto’s project-root convention). Per-shortcode extension filters:includeaccepts markdown/script files (.qmd,.md,.markdown,.rmd,.rmarkdown,.ipynb,.R,.py,.jl);embedaccepts.ipynb/.qmd;videoaccepts the video subset;placeholderaccepts still-image formats. Named args (e.g.echo=true) and URL prefixes (https://...) are skipped. Cell-id completion after{{< embed file.ipynb#... >}}is not yet supported.
Symbol Renaming
Rename references and their definitions together. Place cursor on a reference label or definition and trigger rename (F2 in many editors).
Linked Editing
Edit a symbol and its linked occurrences in the current document at the same time (textDocument/linkedEditingRange). When your cursor enters a reference label, footnote id, citation key, crossref, chunk label, or heading id, a supporting editor highlights every linked span and applies your keystrokes to all of them live—a lightweight, in-document alternative to a full rename. Linked editing is intra-document only (by protocol) and only links spans whose source text is identical, so differently cased or spaced occurrences are left untouched.
Enable it in VS Code with the “Editor: Linked Editing” setting; in Neovim, call vim.lsp.linked_editing.enable() (or use a plugin that drives the request).
Find References
Find all references to the symbol under cursor (textDocument/references), including Quarto crossrefs, executable chunk labels, citation keys, footnote labels, and reference-link labels. The includeDeclaration flag is honored, so clients can choose whether the definition itself is part of the result set.
Document Highlight
Highlight every occurrence of the symbol under the cursor within the current document (textDocument/documentHighlight). Place the cursor on a reference label, citation key, footnote, heading id, crossref, or chunk label and all of its other occurrences—including the definition—are marked. Unlike Linked Editing, highlighting does not require the spans to share identical source text, so a differently-cased definition ([foo]: for a [Foo] usage) is still highlighted. Most editors trigger this automatically as the cursor moves.
Workspace Symbols
Search for headings by name across the whole project, not just the current file (workspace/symbol). The index spans every open document plus the documents reachable through the project graph, and results carry their container (the parent heading hierarchy) so identically named sections stay distinguishable.
Document Links
The LSP exposes clickable links so editors can ctrl/cmd-click to follow them (textDocument/documentLink, resolved lazily via documentLink/resolve):
- Inline links and images (
[text](url),) - Reference links and images (resolved to their
[ref]: urldefinition) - Autolinks (
<https://example.com>) {{< include path >}}shortcode paths
File Rename
When you rename a file in an LSP-aware editor, the server returns a workspace edit that rewrites references pointing at the old path (workspace/willRenameFiles), so cross-document references stay intact. The server rewrites:
- Inline and reference links and images (
[text](path),) - Path-bearing Quarto shortcodes:
{{< include >}},{{< embed >}},{{< video >}},{{< placeholder >}}(fragments likenotebook.ipynb#cellare preserved; external URLs are left untouched) - Document frontmatter file paths:
bibliography,csl, andcss(scalar or block-list values) _quarto.ymlconfig paths:bibliography, navbarhref, and bookpartentries
Nested config paths inside frontmatter (e.g. format.html.css) are not yet handled.
File Operations
After a file is created, renamed, or deleted in the editor (workspace/didCreateFiles, didRenameFiles, didDeleteFiles), the server refreshes its project graph so cross-document diagnostics stay accurate: a newly created include target clears the dependent’s include-not-found, and deleting or renaming a referenced file surfaces broken references where they remain. A deleted file’s own diagnostics are cleared immediately.
These notifications are hygiene-only. Creating a file inserts no scaffolding, and deleting one never rewrites references—unlike rename, which returns a workspace edit (see above), create and delete only update server state and let stale references show up as diagnostics.
Semantic Tokens
The LSP provides semantic tokens (textDocument/semanticTokens/full) for syntax highlighting. This is deliberately additive, not a replacement for your editor’s built-in highlighter: editors already highlight Markdown with CommonMark/GFM grammars (tree-sitter, TextMate) that are not flavor-aware and miss Pandoc/Quarto-specific syntax. Panache only emits tokens for the constructs those grammars get wrong, layering on top of the base highlighting:
- Citation keys (
@key,[@key]) - Cross-reference keys (
@fig-1,\@ref(...)—Quarto/RMarkdown only) - Quarto shortcodes (
{{< name args >}}) - Fenced-div info strings (
{.callout-note}) - Math delimiters (
$,$$) - Footnote references (
[^id]) - Bracketed-span attributes (
[text]{.smallcaps})
Base constructs (headings, emphasis, links, code) are left to your editor so Panache never recolors highlighting you were happy with.
Flavor-gated: documents in CommonMark or GFM flavor receive no tokens — Panache’s parse and the editor’s base grammar agree there, so there is nothing to add.
Custom legend, opt-in colors. The token types are custom (citation, crossref, shortcode, div, math, footnote, attribute), so they have no color in any theme until you map them. This is why advertising the feature by default is harmless: unmapped types render as ordinary text. To see them, map the types to highlight groups.
In VS Code (settings.json):
"editor.semanticTokenColorCustomizations": {
"enabled": true,
"rules": {
"citation": "#c586c0",
"crossref": "#4ec9b0",
"shortcode": "#dcdcaa",
"div": "#569cd6",
"math": "#ce9178",
"footnote": "#9cdcfe",
"attribute": "#b5cea8"
}
}In Neovim, link the LSP semantic-token groups to your colorscheme:
for token, group in pairs({
citation = "@markup.link",
crossref = "@markup.link.label",
shortcode = "@function.macro",
div = "@keyword.directive",
math = "@markup.math",
footnote = "@markup.link.label",
attribute = "@attribute",
}) do
vim.api.nvim_set_hl(0, "@lsp.type." .. token, { link = group })
endLSP Specification Coverage
The table below summarizes Panache’s coverage of the Language Server Protocol. Many spec methods target compiled-language tooling (call hierarchy, type definitions, and so on) and have no meaningful analogue for prose; those are marked out of scope.
| LSP method | Status | Notes |
|---|---|---|
textDocument/didOpen, didChange, didSave, didClose |
✅ | Incremental sync |
textDocument/formatting |
✅ | Honors exclude patterns |
textDocument/rangeFormatting |
✅ | Bypasses excludes by design |
textDocument/definition |
✅ | Links, footnotes, citations, crossrefs, headings |
textDocument/references |
✅ | Honors includeDeclaration |
textDocument/rename, prepareRename |
✅ | Citations, refs, footnotes, crossrefs |
textDocument/hover |
✅ | Footnotes, citations, section/equation previews |
textDocument/completion |
✅ | Citations, crossrefs, file/shortcode paths |
completionItem/resolve |
✅ | Lazy citation previews (bibliography entry) |
textDocument/codeAction |
✅ | Lint fixes + list/footnote/link/heading conversions |
codeAction/resolve |
❌ | Planned: lazy edits + advertised action kinds |
textDocument/documentSymbol |
✅ | Headings, tables, figures, frontmatter |
textDocument/foldingRange |
✅ | Headings, code, divs, HTML, frontmatter |
textDocument/documentLink, documentLink/resolve |
✅ | Links, images, autolinks, includes |
textDocument/publishDiagnostics |
✅ | Push model, debounced |
textDocument/diagnostic, workspace/diagnostic |
✅ | Pull model; push suppressed; partialResultToken streaming |
workspace/diagnostic/refresh |
✅ | Nudge pull clients to re-pull on async updates |
textDocument/documentHighlight |
✅ | Occurrences of the symbol under the cursor |
textDocument/selectionRange |
❌ | Planned: structural smart-select |
textDocument/linkedEditingRange |
✅ | Live co-editing; current document, identical text |
textDocument/onTypeFormatting |
❌ | Under consideration |
textDocument/semanticTokens/full |
✅ | Additive, flavor-gated; custom legend (see above) |
textDocument/inlayHint |
❌ | Under consideration |
workspace/symbol |
✅ | Heading search across the project |
workspace/willRenameFiles |
✅ | Links, shortcodes, frontmatter/config file paths |
workspace/didChangeWatchedFiles |
✅ | Bibliography, config, and document files |
workspace/didCreateFiles, didRenameFiles, didDeleteFiles |
✅ | Hygiene-only: refresh cross-document diagnostics |
workspace/didChangeConfiguration, workspace/configuration |
❌ | Planned: runtime config reload |
workspace/didChangeWorkspaceFolders |
❌ | Advertised but not yet handled |
workspace/executeCommand |
❌ | Planned: commands backing complex actions |
workspace/willCreateFiles, willDeleteFiles |
⛔ | Intentionally omitted: no scaffolding/auto-deletion |
willSave, willSaveWaitUntil |
❌ | Format-on-save runs client-side instead |
Call/type hierarchy, implementation, typeDefinition, declaration, inlineValue, moniker, document color, code lens |
⛔ | Out of scope for prose |
Troubleshooting
LSP Not Starting
Check that panache is in your PATH:
which panacheTest the LSP manually:
panache lsp
# Should start and wait for JSON-RPC inputFormatting Not Working
Enable LSP logging in your editor to see error messages:
Neovim
vim.lsp.set_log_level("debug")
-- View logs: :lua vim.cmd('e'..vim.lsp.get_log_path())VS Code
Set "panache.trace.server": "verbose" in settings.
Configuration Not Loading
Verify your config file is valid by testing with the CLI first:
panache format --config .panache.toml test.qmdThe LSP searches for config from the workspace root, not the file’s directory.