---
title: Language Server Protocol
---

## Overview

panache includes a built-in Language Server Protocol (LSP) implementation that
provides document formatting capabilities for editors and IDEs.

## Features

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`

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

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

Auto-completion
:   Smart completions for Markdown syntax (`textDocument/completion`)

Symbol renaming
:   Rename references and their definitions together (`textDocument/rename`)

Document tracking
:   Incremental synchronization for efficiency (`textDocument/didOpen`,
    `textDocument/didChange`, `textDocument/didClose`)

Configuration discovery
:   Automatic detection from workspace root

Format on save
:   Automatic formatting when saving files

## Starting the Server

Start the LSP server:

```bash
panache lsp
```

The server communicates over stdin/stdout using the standard LSP JSON-RPC
protocol.

## Editor Configuration

### Neovim

Using `nvim-lspconfig`:

```lua
-- Add to your LSP configuration file
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 the server
lspconfig.panache.setup{}
```

#### Format on Save

Add this to enable automatic formatting when saving:

```lua
vim.api.nvim_create_autocmd('BufWritePre', {
  pattern = { '*.qmd', '*.md', '*.rmd' },
  callback = function()
    vim.lsp.buf.format({ async = false })
  end,
})
```

#### Manual Formatting

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

#### Option 1: Custom LSP Extension

Install the [Custom
LSP](https://marketplace.visualstudio.com/items?itemName=josa.custom-lsp)
extension, then configure in `settings.json`:

```json
{
  "customLsp.servers": {
    "panache": {
      "command": "panache",
      "args": ["lsp"],
      "filetypes": ["quarto", "markdown", "rmarkdown"]
    }
  },
  "editor.formatOnSave": true
}
```

#### Option 2: Generic LSP Client

Install a generic LSP client extension, then configure:

```json
{
  "languageServerExample.server": {
    "command": "panache",
    "args": ["lsp"],
    "filetypes": ["quarto", "markdown", "rmarkdown"]
  },
  "editor.formatOnSave": true,
  "[quarto]": {
    "editor.defaultFormatter": "languageServerExample"
  },
  "[markdown]": {
    "editor.defaultFormatter": "languageServerExample"
  }
}
```

### Helix

Add to `~/.config/helix/languages.toml`:

```toml
[[language]]
name = "markdown"
language-servers = ["panache-lsp"]
auto-format = true

[[language]]
name = "quarto"
language-servers = ["panache-lsp"]
auto-format = true

[language-server.panache-lsp]
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`

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

### Symbol Renaming

Rename references and their definitions together. Place cursor on a reference
label or definition and trigger rename (F2 in many editors).

### Document Synchronization

The LSP tracks document changes using **incremental synchronization**:

`textDocument/didOpen`
:   Document opened in editor

`textDocument/didChange`
:   Document content changed (incremental deltas sent)

`textDocument/didClose`
:   Document closed

This ensures the LSP always has the current document state while minimizing
data transfer.

## 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 `"customLsp.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.

### Performance Issues

For large documents:

Reduce line width
:   Lower `line-width` in config to reduce rewrapping work

Disable external formatters
:   Temporarily disable external formatters for faster feedback

Use preserve mode
:   Set `wrap = "preserve"` instead of `"reflow"`

## Comparison with CLI

| Feature             | LSP                      | CLI                    |
| ------------------- | ------------------------ | ---------------------- |
| Formatting          | ✅ Same engine           | ✅ Direct              |
| Linting             | ✅ Live diagnostics      | ✅ On-demand           |
| Config discovery    | ✅ From workspace root   | ✅ From file directory |
| Format on save      | ✅ Automatic             | ❌ Manual              |
| Multiple files      | ✅ Per-editor-tab        | ✅ Shell scripting     |
| Error reporting     | ✅ In-editor diagnostics | ✅ stderr output       |
| External formatters | ✅ Supported             | ✅ Supported           |
| External linters    | ✅ Live integration      | ✅ On-demand           |
| Go to definition    | ✅ LSP only              | ❌ Not applicable      |
| Code actions        | ✅ LSP only              | ❌ Not applicable      |

## Best Practices

Place config at project root
:   Put `.panache.toml` at your project root for consistent behavior across CLI
    and LSP

Use format on save
:   Enable format-on-save for automatic formatting

Test CLI first
:   Verify formatting with CLI before relying on LSP

Check logs
:   Enable LSP logging when debugging issues

Restart after config changes
:   Restart LSP server after changing `.panache.toml`
