ts-bridge
ts-bridge is a standalone TypeScript language-server shim written in Rust. In
this context standalone means the Neovim-facing bits ship as a single Rust
binary—not that TypeScript itself has been rewritten. The binary still launches
the official tsserver that ships with TypeScript and simply orchestrates the
LSP ↔ TypeScript Server conversations.
ts-bridge sits between Neovim's built-in LSP client and tsserver, translating
LSP requests into the TypeScript server protocol (and vice‑versa) while offering
a clear, modular architecture (config, provider, process, protocol,
etc.) that mirrors how modern JS/TS tooling pipelines are organized.
What “standalone” does not mean: This project does not replace
tscortsserver. You still need a standard TypeScript installation, and all type-checking/completions semantics come from Microsoft's compiler. What you gain is a single Rust binary that handles the Neovim side (startup, diagnostics/logging, worker orchestration) without additional Lua or Node glue.
Prerequisites
- Node.js 18+ with a matching TypeScript/
tsserverinstallation discoverable via your workspace (localnode_modulespreferred, but global/npm/Nix paths are fine too).ts-bridgedelegates all language intelligence to thistsserver; it only provides the Rust shim and orchestrator. - Neovim 0.11+ so the built-in LSP client matches the capabilities advertised
by
ts-bridge(semantic tokens, inlay hints, etc.).
Building
You need Rust and Cargo installed. Then clone the repo and run:
The resulting binary (target/release/ts-bridge) can be pointed to from your
Neovim LSP configuration (built-in vim.lsp.config or nvim-lspconfig).
Downloading prebuilt binaries
Get the latest release artifact from the GitHub Releases page.
Install script (Linux/macOS)
The install script downloads the latest release archive from GitHub and places
ts-bridge in ~/.local/bin (override with --install-dir).
|
If you already cloned the repo:
To install a specific version:
The script requires curl or wget plus tar. Checksum verification uses
sha256sum (Linux) or shasum (macOS) when available.
GitHub’s /releases/latest points to the newest non‑pre‑release tag, so you do
not need to create a separate “latest” tag. Use --version to pin a specific
release (including pre‑releases).
Install script (Windows PowerShell)
The PowerShell script downloads the Windows release archive and installs
ts-bridge.exe into %LOCALAPPDATA%\Programs\ts-bridge\bin by default.
powershell -NoProfile -ExecutionPolicy Bypass -Command "Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/chojs23/ts-bridge/main/scripts/install.ps1' | Invoke-Expression"
If you already cloned the repo:
.\scripts\install.ps1
To install a specific version:
.\scripts\install.ps1 -Version v0.4.0
Pass -InstallDir to override the destination or -NoVerify to skip checksum
verification.
LSP Feature Progress
-
initialize/initializedhandshake & server capabilities -
textDocument/didOpen/didChange/didClose(updateOpenbridging) - Diagnostics pipeline (
geterr, semantic/syntax/suggestion batching) -
textDocument/hover(quickinfo) -
textDocument/definition(definitionAndBoundSpan) -
textDocument/typeDefinition(typeDefinition) -
textDocument/references(references) -
textDocument/completion(+completionItem/resolve) -
textDocument/signatureHelp(signatureHelp) -
textDocument/publishDiagnosticsstreaming -
workspace/didChangeConfiguration -
textDocument/documentHighlight -
textDocument/codeAction/codeAction/resolve(quick fixes, organize imports; refactors pending) -
textDocument/rename/workspace/applyEdit(prepare + execute) -
textDocument/formatting/ on-type formatting -
textDocument/implementation -
workspace/symbol/textDocument/documentSymbol - Semantic tokens
- Inlay hints
- Code lens
- Custom commands / user APIs (organize imports, fix missing imports, etc.)
- Dual-process (semantic diagnostics server) feature gating (experimental)
Configuration
ts-bridge works out of the box. For Neovim 0.11+ (built-in LSP config), use:
vim..
vim..
tsserver.preferences and tsserver.format_options are forwarded to
tsserver’s configure request (keys are passed through as-is).
If you're using nvim-lspconfig, the equivalent registration is:
local configs = require
local util = require
if not configs.
local lspconfig = require
lspconfig..
If you built locally instead of installing, swap cmd = { "ts-bridge" } for the
absolute binary path (for example, cmd = { "/path/to/ts-bridge" }).
Because ts-bridge delays spawning tsserver until the first routed request,
these defaults (or any overrides you make) apply to both syntax and semantic
processes before they boot. Restart your LSP client after changing the snippet
so a fresh tsserver picks up the new arguments.
Daemon mode
Daemon mode keeps a single ts-bridge process alive and reuses warm tsserver
instances across LSP clients. It listens on a TCP address or a Unix socket and
accepts normal LSP JSON-RPC connections.
At a high level, the daemon accepts many LSP connections and routes each
project's requests through a shared tsserver service keyed by project root.
┌─────────────────────────────────────────────────┐
│ ts-bridge daemon │
│ │
LSP client 1 ─┤ session (per client) ─┐ │
LSP client 2 ─┤ session (per client) ─┼── Project registry ─┐ │
LSP client 3 ─┤ session (per client) ─┘ │ │
│ │ │
│ project root A ── tsserver (A) │ │
│ project root B ── tsserver (B) │ │
└─────────────────────────────────────────────────┘
Lifecycle summary:
- A client connects over TCP or a Unix socket and completes the normal LSP
initializehandshake. - The daemon selects (or creates) a project entry based on the client’s
workspace root and reuses the warm
tsserverfor that project. - Each session keeps its own open document state and diagnostics routing, but
requests and responses go through the shared
tsserverprocess. - Idle project entries (no active sessions) are evicted after the idle TTL and
their
tsserverprocesses are shut down.
Neovim (auto-start the daemon)
If you prefer not to start the daemon manually, you can spawn it from Neovim and
still connect via vim.lsp.rpc.connect. This runs once per session and reuses
the existing daemon if it is already running:
local
local
local
vim..
vim..
The cmd wrapper ensures the daemon is running before the TCP connection is
attempted (since before_init runs after the transport is created).
If you're using nvim-lspconfig, use:
require..
Note: cmd_env does not apply when using vim.lsp.rpc.connect, so any daemon
settings and logging must be passed in the jobstart environment or CLI args.
When launching with ts-bridge daemon, TS_BRIDGE_DAEMON_* environment values
are not read; use --idle-ttl (and other flags) or run the env-only daemon mode
(TS_BRIDGE_DAEMON=1 without the daemon subcommand).
Running the daemon manually
Optional knobs:
--socket /path/to/ts-bridge.sock(Unix only)--idle-ttl 1800(seconds) or--idle-ttl 30m(suffixs,m,h)--idle-ttl offto disable idle eviction
Environment variable equivalents (only when running ts-bridge without args
with TS_BRIDGE_DAEMON=1):
TS_BRIDGE_DAEMON=1to start daemon mode when runningts-bridgewithout argsTS_BRIDGE_DAEMON_LISTEN=127.0.0.1:7007TS_BRIDGE_DAEMON_SOCKET=/path/to/ts-bridge.sockTS_BRIDGE_DAEMON_IDLE_TTL=30m(oroff)
The default idle TTL is 30 minutes; idle projects (no sessions) are evicted and
their tsserver processes are shut down once they exceed the TTL.
Neovim (daemon connection)
When connecting to a running daemon, use vim.lsp.rpc.connect and register the custom
server config:
vim..
vim..
If you're using nvim-lspconfig instead of the built-in config, use:
local configs = require
local util = require
if not configs.
require..
Daemon settings (listen address, idle TTL, etc.) must be configured on the
daemon process itself; they are not part of LSP settings.
Daemon status request
To inspect the daemon’s current projects and sessions, send the custom LSP
request ts-bridge/status from any connected client:
vim..
The response includes a projects array with fields such as root,
session_count, session_ids, last_used_epoch_seconds, and tsserver PIDs.
Contributing
Every contributions are welcome! Feel free to open issues or submit pull requests.