# pi-lsp-extension A [pi](https://github.com/mariozechner/pi-mono) coding agent extension that integrates Language Server Protocol (LSP) servers, giving the LLM access to the same language intelligence that powers your IDE. ## Tools | Tool | Description | |------|-------------| | `lsp_diagnostics` | Compilation errors and warnings for a file | | `lsp_hover` | Type information and documentation at a position | | `lsp_definition` | Go to definition of a symbol | | `lsp_references` | Find all references to a symbol | | `lsp_symbols` | List file symbols or search workspace symbols | | `lsp_rename` | Preview rename refactoring (returns planned edits) | | `lsp_completions` | Code completion suggestions at a position | | `code_overview` | Project structure, key files, and symbols (tree-sitter) | | `code_search` | Find code by AST structure with metavariables | | `code_rewrite` | Transform code matching structural patterns | LSP servers start lazily — they only spin up when a tool is first used on a file of that language. For slow servers (e.g. jdtls), you can [auto-start them on session launch](#project-config). ## Auto-diagnostics After a successful `write` or `edit`, if an LSP server is already running for that file type, the extension automatically appends compilation errors to the tool result. This gives the LLM immediate feedback without requiring a separate `lsp_diagnostics` call. - Scoped to the single changed file (no workspace-wide noise) - Only errors, max 10 lines — keeps context lean - Only fires when a server is already running (no lazy startup) ## Installation ```bash git clone https://github.com/samfoy/pi-lsp-extension.git cd pi-lsp-extension npm install ``` Add to your pi `settings.json`: ```json { "extensions": ["/path/to/pi-lsp-extension/src/index.ts"] } ``` Or run directly: ```bash pi -e /path/to/pi-lsp-extension/src/index.ts ``` ## Supported Languages Install the language server you need, then it works automatically: | Language | Server | Install | |----------|--------|---------| | TypeScript/JavaScript | `typescript-language-server` | `npm i -g typescript-language-server typescript` | | Python | `pyright-langserver` | `pip install pyright` | | Rust | `rust-analyzer` | [rustup](https://rustup.rs/) | | Go | `gopls` | `go install golang.org/x/tools/gopls@latest` | | Java | `jdtls` | [Eclipse JDT.LS](https://github.com/eclipse-jdtls/eclipse.jdt.ls) | Add more at runtime: ``` /lsp-config ruby solargraph stdio /lsp-config lua lua-language-server ``` ## Commands | Command | Description | |---------|-------------| | `/lsp` | Show status of running LSP servers | | `/lsp-restart ` | Restart an LSP server (kills daemon, re-initializes) | | `/lsp-config [args]` | Configure a language server | | `/lsp-lombok [path]` | Set Lombok jar path for Java (or show current) | | `/bemol [run\|watch\|stop\|status]` | Manage bemol (Brazil workspaces) | ## How it Works 1. **Lazy startup** — servers start on first tool use for a file type (or eagerly via [`.pi-lsp.json`](#project-config)) 2. **Tree-sitter fallback** — when no LSP server is running, tools like `lsp_diagnostics`, `lsp_hover`, `lsp_definition`, and `lsp_symbols` fall back to tree-sitter for syntax errors, signatures, and symbol extraction 3. **File sync** — pi's `read`/`write`/`edit` operations are automatically synced to the LSP via `didOpen`/`didChange`, with LRU eviction (`didClose`) after 100 tracked files 4. **Diagnostics cache** — the server pushes diagnostics asynchronously; tools read from a local cache 5. **Auto-diagnostics** — errors are appended to write/edit results when a server is running 6. **Shared daemons** — in supported workspaces, LSP servers run as background daemons shared across pi sessions ## Lombok Support (Java) If your Java project uses [Lombok](https://projectlombok.org/), jdtls needs the Lombok agent jar to understand generated code. The extension resolves the jar in this order: 1. **`/lsp-lombok` command** — set the path at runtime: ``` /lsp-lombok /path/to/lombok.jar ``` 2. **`LOMBOK_JAR` environment variable** — set before starting pi: ```bash export LOMBOK_JAR=/path/to/lombok.jar pi ``` 3. **Auto-detection** — in Brazil workspaces, the extension searches `env/Lombok-*/runtime/lib/` and `env/gradle-cache-2/` automatically. Run `/lsp-lombok` with no arguments to see which jar is currently configured. ## Project Config Create a `.pi-lsp.json` file in your project root to configure LSP behavior per-project: ```json { "autoStart": ["java", "typescript"], "lombokJar": "auto", "autoInjectDiagnostics": ["typescript"], "servers": { "python": { "command": "pylsp", "args": [] } } } ``` | Field | Description | |-------|-------------| | `autoStart` | Array of language IDs to start eagerly on session launch. Servers begin initializing in the background immediately — no need to wait for the first tool call. Ideal for slow servers like `jdtls`. | | `lombokJar` | Path to a Lombok jar (absolute or relative to project root), or `"auto"` to auto-detect in Brazil workspaces. Applied before auto-start so jdtls launches with the correct `-javaagent` flag. | | `autoInjectDiagnostics` | Controls whether LSP errors are auto-appended to `write`/`edit` tool results. `true` (default) enables for all languages, `false` disables entirely, or pass an array of language IDs (e.g. `["typescript"]`) to enable selectively. Disable for Java/Brazil workspaces where Lombok and dependency-chain false positives create noise. | | `servers` | Custom server configs keyed by language ID. Overrides the built-in defaults. Each entry has `command`, optional `args` (string array), and optional `env` (key-value pairs). | The config file is loaded once at session start. Changes require restarting the pi session. **Example for a Java Brazil workspace:** ```json { "autoStart": ["java"], "lombokJar": "auto" } ``` This triggers bemol + jdtls startup as soon as the session begins, so by the time you need `lsp_diagnostics` or `lsp_hover`, the server is already warm. ## Architecture ``` src/ ├── index.ts # Extension entry point, .pi-lsp.json config loader ├── lsp-client.ts # JSON-RPC client (stdio + socket modes) ├── lsp-manager.ts # Server lifecycle, per-language instances ├── file-sync.ts # didOpen/didChange tracking ├── lsp-daemon.ts # Background daemon for shared servers ├── lsp-daemon-launcher.cjs ├── bemol.ts # Brazil workspace support ├── locks.ts # File-based locking for daemon coordination ├── resolve-provider.ts # LSP vs tree-sitter provider selection ├── shared/ │ ├── constants.ts # Skip dirs, file size limits │ ├── debug.ts # Debug logger (PI_LSP_DEBUG=1) │ ├── format.ts # Location formatting utilities │ ├── language-map.ts # File extension → language ID mapping │ └── timing.ts # Timing constants ├── tree-sitter/ │ ├── parser-manager.ts # WASM parser loading and caching │ ├── pattern-compiler.ts # Metavariable pattern → AST matcher │ ├── search-engine.ts # Structural search over files │ ├── rewrite-engine.ts # Structural find-and-replace │ ├── symbol-extractor.ts # Per-language symbol extraction │ └── workspace-index.ts # Project-wide symbol index └── tools/ ├── diagnostics.ts ├── hover.ts ├── definition.ts ├── references.ts ├── symbols.ts ├── rename.ts ├── completions.ts ├── code-overview.ts ├── code-search.ts └── code-rewrite.ts ``` ## Tips - Position parameters are 1-indexed (line 1, column 1 = first character) - `lsp_rename` returns a preview — the LLM uses `edit`/`write` to apply changes - Use `/lsp-restart java` after changing Lombok config — jdtls needs a full restart to pick up `-javaagent` changes - Set `PI_LSP_DEBUG=1` to enable debug logging for troubleshooting - The extension adds a system prompt guideline nudging the LLM to check diagnostics after edits ## License MIT