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

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.

### 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

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 `![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/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.
