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
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/resolve when an item is focused
  • Reference labels
  • File paths inside [](...) and ![](...) destinations
  • File paths inside Quarto shortcodes (include, embed, video, placeholder)
Symbol renaming

Rename references and their definitions together (textDocument/rename, textDocument/prepareRename)

  • Citation keys
  • Reference labels
  • Footnote labels
  • Quarto crossref labels (including executable chunk label options)
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
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.toml and apply client settings without a restart when the editor sends workspace/didChangeConfiguration or 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.toml fails 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-time window/showMessage notification, 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.panache

If you prefer to install from the VS Code UI, launch VS Code Quick Open (Ctrl+P), then run:

ext install jolars.panache

The 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:

  1. Install LSP package
  2. 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:

  1. Enable the LSP Client plugin
  2. Add to LSP client configuration:
{
  "servers": {
    "markdown": {
      "command": ["panache", "lsp"],
      "highlightingModeRegex": "^Markdown$"
    }
  }
}

Configuration Discovery

The LSP automatically discovers configuration files from your workspace:

  1. Searches for .panache.toml or panache.toml from workspace root
  2. Falls back to ~/.config/panache/config.toml
  3. Uses built-in defaults if no config found

Workspace Root Detection

The LSP determines the workspace root by looking for:

  • .panache.toml or panache.toml
  • .git directory
  • 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., jarl for 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, and metadata-files: includes—are published as diagnostics on the manifest file’s own URI (much as rust-analyzer flags a broken Cargo.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/publishDiagnostics notifications as it computes them. This is the model for clients that don’t advertise pull support.
Pull

Clients that advertise textDocument.diagnostic support are served via the pull model instead (textDocument/diagnostic and workspace/diagnostic), and push is suppressed for them so diagnostics aren’t reported twice. The server advertises inter_file_dependencies (an edit can change another file’s diagnostics) and workspace_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/refresh when 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 an unchanged report when the document’s diagnostics haven’t changed since the client’s last result_id.

Clients that additionally advertise relatedDocumentSupport receive cross-file diagnostics inline: a textDocument/diagnostic pull for one document carries the diagnostics of the other files in its project graph (includes, project siblings, and manifests) under relatedDocuments, so a duplicate label defined in an included file surfaces without waiting for a separate workspace/diagnostic pull. Clients without that capability still receive the same cross-file diagnostics through the workspace pull.

A client that supplies a partialResultToken on either pull receives the report incrementally: the response delivers the first chunk and the remainder streams as $/progress notifications keyed by that token (workspace/diagnostic chunks the per-document reports; textDocument/diagnostic chunks the relatedDocuments map). 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 through completionItem/resolve when the item is focused, so large .bib files don’t pay the formatting cost up front.
  • File paths inside link and image destinations—typing inside [text](...) or ![alt](...) suggests 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 .qmd documents, 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: include accepts markdown/script files (.qmd, .md, .markdown, .rmd, .rmarkdown, .ipynb, .R, .py, .jl); embed accepts .ipynb/.qmd; video accepts the video subset; placeholder accepts 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.

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), ![alt](path))
  • Path-bearing Quarto shortcodes: {{< include >}}, {{< embed >}}, {{< video >}}, {{< placeholder >}} (fragments like notebook.ipynb#cell are preserved; external URLs are left untouched)
  • Document frontmatter file paths: bibliography, csl, and css (scalar or block-list values)
  • _quarto.yml config paths: bibliography, navbar href, and book part entries

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 })
end

LSP 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 panache

Test the LSP manually:

panache lsp
# Should start and wait for JSON-RPC input

Formatting 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.qmd

The LSP searches for config from the workspace root, not the file’s directory.