Skip to content
/ panache Public

An LSP, formatter, and linter for Pandoc markdown, Quarto, and RMarkdown

License

Notifications You must be signed in to change notification settings

jolars/panache

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

611 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

panache

Build and Test Crates.io codecov

A formatter, linter, and LSP for Quarto (.qmd), Pandoc, and Markdown files.

Work in Progress

This project is in early development. Expect bugs, missing features, and breaking changes.

Installation

From crates.io (Recommended)

cargo install panache

Pre-built Binaries

Download pre-built binaries from the releases page. Available for:

  • Linux (x86_64, ARM64)
  • macOS (Intel, Apple Silicon)
  • Windows (x86_64)

Each archive includes the binary, man pages, and shell completions.

Linux Packages

For Debian/Ubuntu systems:

# Download the .deb from releases
sudo dpkg -i panache_*.deb

For Fedora/RHEL/openSUSE systems:

# Download the .rpm from releases
sudo rpm -i panache-*.rpm

Packages include:

  • Binary at /usr/bin/panache
  • Man pages for all subcommands
  • Shell completions (bash, fish, zsh)

Usage

Formatting

# Format a file in place
panache format document.qmd

# Check if a file is formatted
panache format --check document.qmd

# Format from stdin
cat document.qmd | panache format

# Format all .qmd and .md files in directory, recursively
panache format **/*.{qmd,md}

Linting

# Lint a file
panache lint document.qmd

# Lint entire working directory
panache lint .

Language Server (LSP)

panache includes a built-in Language Server Protocol implementation for editor integration.

Features:

  • Document formatting (full document and range)
  • Live diagnostics with quick fixes
  • Code actions for refactoring
    • Convert between loose/compact lists
    • Convert between inline/reference footnotes
  • Document symbols/outline
  • Folding ranges
  • Go to definition for references and footnotes

Start the server:

panache lsp

Editor Configuration:

The LSP communicates over stdin/stdout and provides document formatting capabilities.

Neovim (using 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" },
			root_dir = lspconfig.util.root_pattern(".panache.toml", "panache.toml", ".git"),
			settings = {},
		},
	}
end

-- Enable it
lspconfig.panache.setup({})

Format on save:

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

Install a generic LSP client extension like vscode-languageserver-node, then configure in settings.json:

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

Or use the Custom LSP extension.

Helix

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

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

[language-server.panache-lsp]
command = "panache"
args = ["lsp"]

Configuration: The LSP automatically discovers .panache.toml from your workspace root.

Configuration

panache looks for a configuration in:

  1. .panache.toml or panache.toml in current directory or parent directories
  2. ~/.config/panache/config.toml

Example config

# Markdown flavor and line width
flavor = "quarto"
line-width = 80
line-ending = "auto"

# Formatting style
[style]
wrap = "reflow"

# External code formatters (opt-in)
[formatters]
python = ["isort", "black"]  # Sequential formatting
r = "air"                    # Built-in preset
javascript = "prettier"      # Reusable definitions
typescript = "prettier"
yaml = "yamlfmt"             # Formats both code blocks AND frontmatter

# Customize formatters (optional)
[formatters.prettier]
args = ["--print-width=100"]

# External code linters (opt-in)
[linters]
r = "jarl"  # Enable R linting

See .panache.toml.example for a complete configuration reference.

External Code Formatters

panache supports external formatters for code blocks—opt-in and easy to enable:

[formatters]
r = "air"           # Available presets: "air", "styler"
python = "ruff"     # Available presets: "ruff", "black"
javascript = "prettier"
typescript = "prettier"  # Reuse same formatter

Key features:

  • Opt-in by design - No surprises, explicit configuration
  • Built-in presets - Quick setup with sensible defaults
  • Preset inheritance - Override only specific fields, inherit the rest
  • Incremental arg modification - Add args with append_args/prepend_args
  • Sequential formatting - Run multiple formatters in order: python = ["isort", "black"]
  • Reusable definitions - Define once, use for multiple languages
  • Parallel execution - Formatters run concurrently across languages
  • Graceful fallback - Missing tools preserve original code (no errors)
  • Custom config - Full control with cmd, args, stdin fields

Custom formatter definitions:

[formatters]
python = ["isort", "black"]
javascript = "prettier"

# Partial override - inherits cmd/stdin from built-in "air" preset
[formatters.air]
args = ["format", "--custom-flag", "{}"]  # Only override args

# Incremental modification - add args without full override
[formatters.ruff]
append_args = ["--line-length", "100"]  # Adds to preset args

# Full custom formatter
[formatters.prettier]
cmd = "prettier"
args = ["--print-width=100"]
stdin = true

Preset inheritance:

When a [formatters.NAME] section matches a built-in preset name (like air, black, ruff), unspecified fields are inherited from the preset:

[formatters]
r = "air"

[formatters.air]
args = ["format", "--preset=tidyverse"]  # cmd and stdin inherited from built-in

Incremental argument modification:

Use append_args and prepend_args to add arguments without completely overriding the base args (from preset or explicit args field):

[formatters]
r = "air"

[formatters.air]
# Base args from preset: ["format", "{}"]
append_args = ["-i", "2"]
# Final args: ["format", "{}", "-i", "2"]

Both modifiers work together and with explicit args:

[formatters.custom]
cmd = "shfmt"
args = ["-filename", "$FILENAME"]
prepend_args = ["--verbose"]
append_args = ["-i", "2"]
# Final: ["--verbose", "-filename", "$FILENAME", "-i", "2"]

Additional details:

  • Formatters respect their own config files (.prettierrc, pyproject.toml, etc.)
  • Support both stdin/stdout and file-based formatters
  • 30 second timeout per formatter

External Code Linters

panache supports external linters for code blocks—opt-in via configuration:

# Enable R linting
[linters]
r = "jarl"  # R linter with JSON output

Key features:

  • Opt-in by design - Only runs if configured
  • Stateful code analysis - Concatenates all code blocks of same language to handle cross-block dependencies
  • LSP integration - Diagnostics appear inline in your editor
  • CLI support - panache lint shows external linter issues
  • Line-accurate diagnostics - Reports exact line/column locations

How it works:

  1. Collects all code blocks of each configured language
  2. Concatenates blocks with blank-line preservation (keeps original line numbers)
  3. Runs external linter on concatenated code
  4. Maps diagnostics back to original document positions

Supported linters:

  • jarl - R linter with structured JSON output

Note: Auto-fixes from external linters are currently disabled due to byte offset mapping complexity. Diagnostics work perfectly.

Motivation

I wanted a formatter that understands Quarto and Pandoc syntax. I have tried to use Prettier as well as mdformat, but both fail to handle some of the particular syntax used in Quarto documents, such as fenced divs and some of the table syntax.

Design Goals

  • Full LSP implementation for editor integration
  • Linting as part of LSP but also available as a standalone CLI command
  • Support Quarto, Pandoc, and Markdown syntax
  • Fast lossless parsing and formatting (no CST changes if already formatted)
  • Be configurable, but have sane defaults (that most people can agree on)
  • Format math
  • Hook into external formatters for code blocks (e.g. air for R, ruff for Python)

About

An LSP, formatter, and linter for Pandoc markdown, Quarto, and RMarkdown

Topics

Resources

License

Stars

Watchers

Forks

Contributors 4

  •  
  •  
  •  
  •  

Languages