# Language server

> One language server for TypeScript, JavaScript, Python, Rust, C, C++ and C# with bun lsp

`bun lsp` is a language server for editors and a command line for agents and scripts. It speaks the Language Server Protocol and passes each request to the server of the document's language. It starts that server the first time a file of that language needs it.

| Language                | Server                                                                                                  | Diagnostics                                         |
| ----------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| TypeScript, JavaScript  | `tsgo` (`@typescript/native-preview`), else `typescript-language-server`, else `bun x` downloads `tsgo` | [`bun check`](https://aphrody.app/docs/runtime/check), in the same process  |
| Python                  | `ty`, else `basedpyright`, else `pyright`, else `ty` through [`bun uv`](https://aphrody.app/docs/runtime/buv)                   | the server, plus `ruff server` if it is installed   |
| Rust                    | `rust-analyzer`                                                                                         | the server                                          |
| C, C++, Objective-C     | `clangd`, else `clangd` from PyPI through `bun uv`                                                      | the server                                          |
| C#                      | Microsoft `roslyn-language-server` (`Microsoft.CodeAnalysis.LanguageServer`)                            | the server                                          |

`bun lsp` finds servers in `node_modules/.bin`, in the `.venv` of the project, and on `PATH`.

## Editors

Configure `bun lsp` as the language server of every language it handles. It reads requests on stdin and writes answers on stdout. Incoming messages are limited to 64 MiB of JSON and 8 KiB of framing headers, including blank lines and extension headers.

```json Zed settings.json icon="file-code"
{
  "lsp": {
    "bun": { "binary": { "path": "bun", "arguments": ["lsp"] } }
  }
}
```

```lua Neovim icon="file-code"
vim.lsp.config("bun", {
  cmd = { "bun", "lsp" },
  filetypes = { "typescript", "javascript", "typescriptreact", "python", "rust", "c", "cpp", "cs" },
  root_markers = { ".git" },
})
vim.lsp.enable("bun")
```

The editor gets the diagnostics of all of a document's servers in one list. Completions, code actions, inlay hints, code lenses and document links are resolved by the server that returned them. `workspace/symbol` asks every running server.

## Queries from the command line

`bun lsp query` asks one question and prints the answer. The first query of a workspace starts a daemon for it, which keeps the servers running between queries. Later queries take milliseconds instead of the seconds a server needs to load a project.

```bash terminal icon="terminal"
bun lsp query diagnostics src/index.ts
bun lsp query definition src/index.ts:12:8
bun lsp query references src/index.ts:12:8
bun lsp query hover src/index.ts:12:8
bun lsp query symbols src/index.ts
bun lsp query workspace-symbols . Router
bun lsp query rename src/index.ts:12:8 newName --apply
bun lsp query context src/index.ts:12:8
bun lsp query callers src/index.ts:12:8
bun lsp query callees src/index.ts:12:8
bun lsp query fix src/index.ts:12 --apply
```

```txt
src/index.ts:3:7: error TS2322: Type 'string' is not assignable to type 'number'.
```

Lines and columns start at 1, and columns count characters. `bun lsp query diagnostics` exits with code `1` if there is an error.

| Flag              | Description                                                         |
| ----------------- | ------------------------------------------------------------------- |
| `--json`          | Print the answer as JSON                                            |
| `--limit <n>`     | Print at most `n` items. Defaults to `100`                          |
| `--timeout <ms>`  | How long a server may take. Defaults to `30000`                     |
| `--apply`         | With `rename` and `fix`, write the edits to the files               |
| `--no-daemon`     | Start the servers in this process, and stop them after the answer   |

## Code graph and fixes

The daemon builds the code graph of the workspace in the background, and updates it when files change. It holds the calls between functions in Rust, TypeScript, JavaScript and Markdown, and the links between languages that no single server sees: C ABI symbols shared by name between Rust, C, C++ and Zig, the `$rust`, `$cpp` and `$zig` calls of builtin modules, and the Rust methods that `.classes.ts` definitions bind.

- `context` prints what the graph, the server and the workspace docs know of the symbol.
- `callers` and `callees` print the functions that call it or that it calls, with its uses and definitions in other languages.
- `fix` sends the lines around the given line, or around the first error, to the command of `BUN_LSP_AI` with their diagnostics, hover, graph context and docs. The first fenced block of the answer replaces those lines; `--apply` writes it.

```bash terminal icon="terminal"
BUN_LSP_AI="claude -p" bun lsp query fix src/index.ts --apply
```

`bun lsp status` prints the size of each graph and how long it took to build.

The other commands manage the daemon of the workspace in the current directory, or in the given directory:

```bash terminal icon="terminal"
bun lsp warm            # start the daemon and the servers of each language in the workspace
bun lsp warm --lang typescript,rust
bun lsp status          # the running servers
bun lsp stop
bun lsp servers         # the server that each language would start, or what to install
```

The daemon of a workspace exits after 30 minutes without a query. A server stops after 15 minutes without a request. When more than 6 servers would run, the least recently used one stops.

## C and C++

`clangd` needs a `compile_commands.json`. `bun lsp` looks for it in the project root, then in `build/`, `out/` and `cmake-build-debug/` and their subdirectories, with `build/debug` first. If there is none, it writes one with `ninja -t compdb` from the `build.ninja` it finds there, keeping only the compile commands. In a Linux kernel tree, it runs `scripts/clang-tools/gen_compile_commands.py` after a build.

```bash terminal icon="terminal"
bun lsp compdb          # write compile_commands.json now
```

`clangd` keeps its background index next to `compile_commands.json`, in `.cache/clangd`, so it survives restarts. `bun lsp` starts it with `--background-index`, `--limit-results=100`, `-j` set to half the CPU cores (at most 4), and `--pch-storage=disk`. On Windows, when `INCLUDE` is not set, it gives `clangd` the MSVC and Windows SDK environment from [`bun msvc env`](https://aphrody.app/docs/runtime/msvc) so that `clang-cl` command lines find their headers.

## C#

`bun lsp` routes `.cs` files to Microsoft's open-source Roslyn language server. It first uses `BUN_LSP_CSHARP`, then an executable named `roslyn-language-server` or `Microsoft.CodeAnalysis.LanguageServer` on `PATH`. On Windows it also discovers the Roslyn server shipped by the official VS Code C# extension under `.vscode/extensions/ms-dotnettools.csharp-*/.roslyn`. Project roots are selected from `global.json`, `.sln`, `.slnx` and `.csproj` files.

## Configuration

| Environment variable       | Description                                                                                       |
| -------------------------- | ------------------------------------------------------------------------------------------------- |
| `BUN_LSP_TYPESCRIPT`       | The TypeScript server command, such as `"tsgo --lsp --stdio"` or a JSON array of arguments        |
| `BUN_LSP_PYTHON`           | The Python server command                                                                         |
| `BUN_LSP_RUST`             | The Rust server command                                                                           |
| `BUN_LSP_CPP`              | The C and C++ server command. Its arguments replace those that `bun lsp` adds                     |
| `BUN_LSP_CSHARP`           | The C# Roslyn server command, for example `"Microsoft.CodeAnalysis.LanguageServer --stdio"`       |
| `BUN_LSP_TS_DIAGNOSTICS`   | `server` to take TypeScript diagnostics from the server instead of `bun check`                    |
| `BUN_LSP_OFFLINE`          | `1` to never download a server with `bun x` or `bun uv`                                           |
| `BUN_LSP_LIMIT`            | The most items in an answer, and `clangd --limit-results`. Defaults to `100`                      |
| `BUN_LSP_JOBS`             | Threads of `clangd` and `rust-analyzer`. Defaults to half the CPU cores, at most 4                |
| `BUN_LSP_MAX_SERVERS`      | The most servers that run at once. Defaults to `6`                                                |
| `BUN_LSP_SERVER_IDLE_MS`   | How long an unused server keeps running. Defaults to 15 minutes                                   |
| `BUN_LSP_IDLE_MS`          | How long the daemon waits for a query before it exits. Defaults to 30 minutes                     |
| `BUN_LSP_TIMEOUT_MS`       | How long a server may take to answer. Defaults to 30 seconds                                      |
| `BUN_LSP_GRAPH`            | `0` to turn the code graph off                                                                    |
| `BUN_LSP_GRAPH_FILES`      | The most files in a graph. Defaults to `20000`                                                    |
| `BUN_LSP_AI`               | The command that answers a prompt on stdin, for `fix`, such as `"claude -p"` or `"codex exec -"` |

`rust-analyzer` starts with `checkOnSave` off, so it does not run `cargo check` after each save.
