lspd
v1.2.0
Published
LSP multiplexer daemon for sharing language servers across editors
Maintainers
Readme
lspd
LSP multiplexer daemon for sharing language server instances across multiple editors.
This project is Bun/TypeScript-based (it previously had a Rust implementation, which has been removed).
Problem
Two failure modes that lspd handles:
- If you open multiple editor instances on the same project (multiple Neovim windows, OpenCode + Neovim, etc.), each client usually spawns its own language server process. For heavier servers like
tsgo, this wastes a lot of CPU and memory. - Servers like
oxlintandtsgoare happiest when scoped to one config file (.oxlintrc.json,tsconfig.json). Running a single instance over a large monorepo conflates configs and can be slow. Splitting per config keeps state small and per-package settings honored.
Solution
lspd sits between your editor(s) and the language server.
- For
oxlintandtsgo(default): one server process per(nearest config file, server)pair. Files inpackages/foo/use the LSP rooted atpackages/foo/; files inpackages/bar/use a separate LSP. Children are spawned lazily as you open files. - For other servers (or with
--single-process): one server process per(projectRoot, serverName)pair, multiplexed to all editors.
Either way, multiple editor clients can share the same daemon.
Features
- Per-manifest routing for oxlint (
.oxlintrc.json) and tsgo (tsconfig.json,jsconfig.json) - Shared LSP instances across editor clients
- Automatic lifecycle (daemon starts on first connection; exits after last client disconnects)
- Binary discovery (prefers
node_modules/.bin, with env overrides) - Request id translation (prevents collisions across multiple clients)
- Diagnostics bridging for mixed clients
- If a client doesn't support pull diagnostics (
textDocument/diagnostic), the mux bridges pull->push usingtextDocument/publishDiagnostics.
- If a client doesn't support pull diagnostics (
Installation
This package requires bun to be installed (the CLI is a small Node wrapper that spawns Bun).
npm install -g lspd
# or
bun add -g lspdUsage
Connect (used by editors)
lspd connect tsgo --project /path/to/project
lspd connect oxlint --project /path/to/project
# Force one process for the whole project (disables per-manifest routing):
lspd connect oxlint --project /path/to/project --single-processManage daemons
lspd ps
lspd ps --json
lspd kill tsgo --project /path/to/project
lspd kill oxlint --project /path/to/project --single-process
lspd kill --all
lspd pruneYour editor should start lspd connect <server> and speak LSP over stdin/stdout.
Neovim example
-- instead of: cmd = { "tsgo", "--lsp", "-stdio" }
cmd = { "lspd", "connect", "tsgo", "--project", vim.loop.cwd() }Configuration
Environment variables:
LSPD_TSGO_BIN=/absolute/path/to/tsgoLSPD_OXLINT_BIN=/absolute/path/to/oxlint
How it works (high-level)
lspd connect <server>ensures a daemon for(projectRoot, server)is running.- The daemon listens on a Unix domain socket under:
~/.cache/lspd/daemons/<id>/daemon.sock
- All connected editor clients are multiplexed to that daemon.
- Inside the daemon:
- For oxlint / tsgo: each inbound message is routed by its
textDocument.urito a child LSP whose cwd is the file's nearest manifest dir (.oxlintrc.jsonortsconfig.json). Children are spawned lazily. Files outside any manifest fall back to a child rooted at--project. - For other servers (or with
--single-process): one shared child handles everything.
- For oxlint / tsgo: each inbound message is routed by its
- Request ids are translated to avoid collisions; server-initiated requests are forwarded to the primary client and routed back to the originating child.
Caveats for per-manifest mode
- Cross-config requests (e.g. tsgo
find referencesacross packages with separatetsconfig.jsons) only see the file's own subproject. If your monorepo relies on TS project references for cross-package navigation, use--single-processfor tsgo. workspace/symboland similar workspace-wide queries are best-effort: they go to the--projectfallback child only.
Development
bun testNotes:
- The smoke tests use
test-fixtures/projand will runbun installthere as needed. - The
tsgosmoke test may buildtsgofrom source if the prebuilt binary hangs, which requiresgit,go, and network access.
License
MIT (see LICENSE).
