Evan Reichard e143e05758 feat(servers): split vscode-html-language-server and add css, json, bash, sql servers
- Split vscode-html-language-server into separate servers for HTML, CSS,
  and JSON with proper language IDs and file extensions
- Added bash-language-server for shell scripts (.sh, .bash)
- Added sqls for SQL files
- Added timeout wrapper to auto-check diagnostics to prevent blocking pi
2026-05-08 18:51:12 -04:00
2026-04-25 21:06:15 -04:00

evan/pi-lsp

LSP extension for pi coding agent. Provides LSP tools that the LLM can use to query language servers, plus automatic diagnostics after edit/write operations.

Features

LSP Tools (callable by LLM)

  • lsp_hover - Get hover documentation for a symbol
  • lsp_definition - Find the definition of a symbol
  • lsp_references - Find all references to a symbol
  • lsp_completion - Get completion suggestions
  • lsp_documentSymbol - Get the symbol outline of a file
  • lsp_diagnostics - Get lint/type-check diagnostics

Auto-Check

Automatically runs LSP diagnostics after edit or write tool calls. If issues are found, sends a message with the diagnostics to the LLM.

Enable/disable:

pi --lsp-auto-check=false  # Disable auto-check
pi --lsp-auto-check=true   # Enable (default)

Manual Check Command

Run diagnostics manually on specific files:

/lsp-check main.go utils.go

Server Control Commands

Disable a server so this pi instance won't use it (the shared daemon and other instances are unaffected). When all servers are disabled, LSP tools are removed from the active tool set.

Command Args Behavior
/lsp-servers none List running servers and disabled state
/lsp-disable [<id>] Disable all (no arg) or specific server. Bare command disables all.
/lsp-enable [<id>] Enable all (no arg) or specific server. Restores tools when any is enabled.
/lsp-destroy [<id>] Kill running daemon entries for all (no arg) or specific server. Explicitly destructive.
/lsp-disable gopls          # Disable just gopls; other LSP tools still work
/lsp-disable                # Disable all — removes LSP tools from active set
/lsp-enable gopls           # Re-enable gopls; restores tools
/lsp-enable                 # Re-enable all
/lsp-destroy gopls          # Kill running gopls process(es) in the daemon
/lsp-destroy                 # Kill all running server processes

Install

cd ~/.pi/extensions/lsp
npm install

CLI Usage (for development/testing)

tsx ./cli.ts <file> <lsp_command> <req_data_json> [--no-daemon]
tsx ./cli.ts daemon <status|stop>

Requests use a long-lived background daemon by default. The daemon is autospawned on first use, keeps one language server alive per (server.id, project root), and evicts idle servers after ServerConfig.idleTtlMs (default: 5 minutes). Pass --no-daemon to use the legacy one-shot path for debugging.

req_data_json is the raw LSP params for the command, minus textDocument.uri (we inject that from <file>).

Commands

  • hover
  • definition
  • references
  • completion
  • documentSymbol
  • diagnostics (waits briefly for the first publishDiagnostics)

Examples

# Hover at line 224, col 23 (LSP is 0-indexed, so subtract 1)
npm run lsp -- backend/api/server.go hover \
  '{"position":{"line":223,"character":22}}'

# Go to definition
npm run lsp -- backend/api/server.go definition \
  '{"position":{"line":223,"character":22}}'

# Document symbols (no params needed)
npm run lsp -- backend/api/server.go documentSymbol '{}'

# Diagnostics
npm run lsp -- backend/api/server.go diagnostics '{}'

# Inspect/stop the background daemon
npm run lsp -- daemon status
npm run lsp -- daemon stop

Set LSP_DEBUG=1 to forward server stderr to the daemon log. The daemon socket is $XDG_RUNTIME_DIR/pi-lsp-$UID.sock (tmpdir fallback); logs are in /tmp/pi-lsp-daemon.log.

Adding A Server

Edit server.ts:

{
  id: "rust-analyzer",
  match: ["rs"],
  command: "rust-analyzer",
  args: [],
  rootMarkers: ["Cargo.toml"],
  languageId: "rust",
}

Per-Repo Config (.pi-lsp.json)

Drop a .pi-lsp.json at any ancestor of your working files to add or override servers for that repo. The nearest config (walking upward) wins.

{
  "servers": [
    {
      "id": "rust-analyzer",
      "match": ["rs"],
      "command": "rust-analyzer",
      "args": [],
      "rootMarkers": ["Cargo.toml"],
      "languageId": "rust"
    },
    { "id": "gopls", "args": ["-remote=auto", "-vv"] }
  ],
  "disable": ["oxlint"]
}

Merge rules:

  • Entry with a built-in id → fields shallow-merge over the built-in (user wins).
  • Entry with a new id → appended; must include match, command, args, rootMarkers.
  • disable → filters out matching ids (built-in or user-defined).

Reloading after edits: Config is re-read on mtime change, so new lookups pick up changes automatically. However, already-running language servers in the daemon keep their original spawn args. If you change command, args, or rootMarkers, run /lsp-destroy (or pi-lsp daemon stop) so they respawn with the new config.

Security note: .pi-lsp.json controls what binary pi-lsp spawns. Treat it like .vscode/settings.json — don't accept untrusted configs from arbitrary repos.

Adding A Command

  1. Add to the LspCommand union in src/types.ts.
  2. Add a handler in src/commands.ts.

Future

  • Daemon hardening - persistent metrics, health checks, and richer status output.
  • Build output - ship compiled JS entrypoints instead of relying on tsx for development.
Description
No description provided
Readme 490 KiB
Languages
TypeScript 99.6%
Nix 0.4%