Users can now drop a .pi-lsp.json at any ancestor of their working files to add new LSP servers, override built-in ones, or disable servers entirely. The nearest config (walking upward) wins. - New src/config.ts: walks upward for .pi-lsp.json, parses, and merges with the built-in registry. Cached per config-file path with mtime invalidation. Falls back to built-ins on parse error. - Merge rules: matching id shallow-merges (user wins); new id appends (must include match/command/args/rootMarkers); `disable` filters at the end. - src/root.ts: pickServer() now resolves servers via the per-repo registry. Adds findServerById(filePath, id) and re-exports getServersForPath() for callers. - src/daemon.ts: getOrCreateEntry() resolves serverId against the filePath's config so spawned servers reflect repo overrides. - index.ts and cli.ts: replace direct `servers` imports with path-aware getServersForPath() lookups. - Tests: 9 new unit tests covering merge semantics, walk-up discovery, mtime invalidation, and graceful fallback. - Docs: README "Per-Repo Config" section + AGENTS.md updates.
173 lines
5.1 KiB
Markdown
173 lines
5.1 KiB
Markdown
# 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:**
|
|
```bash
|
|
pi --lsp-auto-check=false # Disable auto-check
|
|
pi --lsp-auto-check=true # Enable (default)
|
|
```
|
|
|
|
### Manual Check Command
|
|
|
|
Run diagnostics manually on specific files:
|
|
```bash
|
|
/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. |
|
|
|
|
```bash
|
|
/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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# 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`:
|
|
|
|
```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.
|
|
|
|
```json
|
|
{
|
|
"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.
|