---
title: Language Server
description: >
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](#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`)
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 outline
: Hierarchical view of document structure (`textDocument/documentSymbol`)
- Headings (H1-H6) with proper nesting
- Tables (with captions when available)
- Figures (images with alt text)
Code folding
: Fold sections of your document (`textDocument/foldingRange`)
- Headings
- Code blocks
- Fenced divs
- YAML frontmatter
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)
Code actions
: Quick fixes and refactorings (`textDocument/codeAction`)
- Auto-fix lint issues (e.g., heading hierarchy corrections)
- Convert list styles (loose ↔ compact)
- Convert footnote styles (inline ↔ reference)
- Convert link styles (inline ↔ reference)
Hover information
: Contextual information on hover (`textDocument/hover`)
- Footnote definitions
- Citation previews
Auto-completion
: Smart completions for Markdown syntax (`textDocument/completion`)
- Citation keys
- 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`)
- Citation keys
- Reference labels
- Quarto crossref labels (including executable chunk `label` options)
Document tracking
: Incremental synchronization for efficiency (`textDocument/didOpen`,
`textDocument/didChange`, `textDocument/didClose`)
Configuration discovery
: Automatic detection from workspace root
## Editor Configuration {#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
In Neovim +0.11, you can use the built-in LSP client:
```lua
-- .config/nvim/lsp/panache.lua
return {
cmd = { "panache", "lsp" },
filetypes = { "quarto", "markdown", "rmarkdown" },
root_markers = { ".panache.toml", "panache.toml", ".git" },
settings = {},
}
-- Enable it
vim.lsp.enable({"panache"})
```
For earlier Neovim releases, use `nvim-lspconfig`:
```lua
-- 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" },
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:
```lua
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = { "*.qmd", "*.md", "*.rmd" },
callback = function()
vim.lsp.buf.format({ async = false })
end,
})
```
Format the current buffer with:
```vim
:lua vim.lsp.buf.format()
```
Or map it to a key:
```lua
vim.keymap.set("n", "<leader>f", vim.lsp.buf.format, { desc = "Format buffer" })
```
### VS Code
Install the [VS Code Marketplace
extension](https://marketplace.visualstudio.com/items?itemName=jolars.panache).
The extension starts the language server automatically for supported files.
To install through the command line, run:
```bash
code --install-extension jolars.panache
```
If you prefer to install from the VS Code UI, launch VS Code Quick Open
(`Ctrl+P`), then run:
```sh
ext install jolars.panache
```
The extension launches `panache lsp` automatically.
Optional extension settings:
```json
{
"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](https://open-vsx.org/extension/jolars/panache).
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`:
```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`:
```elisp
(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](https://packagecontrol.io/packages/LSP) package
2. Add to LSP settings:
```json
{
"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:
```json
{
"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.)
## 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:
```vim
" 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.
### Go to Definition
Jump from link/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
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 heading hierarchy violations automatically
Convert list styles
: Toggle between loose (blank lines) and compact list formatting
Convert footnote styles
: Toggle between inline `^[text]` and reference `[^id]` footnotes
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:
```vim
" 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
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
External linters
: Code block linting (e.g., `jarl` for R when configured in `[linters]`)
Diagnostics appear as:
- Squiggly underlines in the editor
- Hover tooltips with error messages
- Problems/diagnostics panel
Quick fixes are available via code actions where applicable.
### 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`).
- **File paths** inside link and image destinations --- typing inside
`[text](...)` or `` 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/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).
### Find References
Find all references to the symbol under cursor (`textDocument/references`),
including Quarto crossrefs, executable chunk labels, and citation keys.
## Troubleshooting
### LSP Not Starting
Check that panache is in your PATH:
```bash
which panache
```
Test the LSP manually:
```bash
panache lsp
# Should start and wait for JSON-RPC input
```
### Formatting Not Working
Enable LSP logging in your editor to see error messages:
#### Neovim
```lua
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:
```bash
panache format --config .panache.toml test.qmd
```
The LSP searches for config from the workspace root, not the file's directory.