- Alpha
- On npm: @ts-r.dev/lsp
- VS Code extension not packaged
ts-r-lsp: language server
One shared, memory-budgeted language server per workspace. Runs your project's TypeScript or the ts-r checker, plus ts-r-lint diagnostics.
ts-r-lsp gives every editor window on a workspace one shared server instead of a TypeScript process each. Each window starts a small front process (about 3 MB) that connects to one daemon per workspace. The daemon runs the type backend, watches memory, and adds lint diagnostics from ts-r-lint.
Type backends
| Backend | When it is used | What you get |
|---|---|---|
| ts-r | ts-r.typeBackend is ts-r (the default), the client speaks LSP, and the lib files are found | The ts-r checker: diagnostics, hover, definition, references, rename, completion, signature help, document symbols, semantic tokens |
| typescript | ts-r.typeBackend is typescript, or the ts-r backend can't be used | Your project's own TypeScript: tsserver 5.x or 6.x, or TypeScript 7's native --lsp --stdio |
The ts-r checker still misses errors tsc reports (see ts-r-check). So when a project opens, the daemon runs your own tsc on it in the background and compares the results. If they differ, that project's diagnostics come from tsserver instead, while hover, definition and the other features stay on ts-r. The result is cached until the tsconfig, the lockfile or the TypeScript version changes. TS_R_LSP_PARITY=off turns the check off.
What works today
- One daemon per workspace, shared by every window, over a Unix socket in the ts-r cache.
- A memory budget: when the backend's memory passes
--memory-budget-mband you have been quiet for 30 seconds, the backend restarts. Open documents are replayed, so the editor doesn't notice. - Idle shutdown of the backend after
--idle-shutdown-minutes; the next request starts it again. The daemon exits 10 seconds after its last window closes. - Lint diagnostics and code actions from ts-r-lint's rules and configs: fixes, suggestions, "Fix all", "Disable for this line" and "Disable for the entire file".
- A tsserver stand-in (
ts-r-lsp tsserver) so VS Code's built-in TypeScript support can share the daemon. - No syntax server and no typings installer, which is part of the memory saving.
Not yet
- VS Code windows talk the tsserver protocol and never reach the ts-r backend. They always use TypeScript.
- Over tsserver, the LSP side has no TypeScript code actions, signature help, document symbols, formatting, semantic tokens or inlay hints.
- The ts-r backend builds a fresh checker on every edit. TSX files with React types are slower per keystroke than tsserver.
- Hover at a call shows the declared signature, not the resolved one. JSX attributes have no types yet.
- Only open files get diagnostics; files that depend on an edited file aren't checked until you open them.
- Parity is all or nothing per project: 22 of 24 private-app projects fall back today.
- The VS Code extension in
editors/vscodeisn't packaged as a.vsixand hasn't been tested inside a running VS Code. - Windows is untested.
The full list is in ts_r_lsp's notes.
Command line
ts-r-lsp: one shared, memory-budgeted language server per workspace
USAGE
ts-r-lsp [--stdio] [OPTIONS] LSP server on stdio (spawns or attaches to the daemon)
ts-r-lsp tsserver [TSSERVER ARGS] tsserver stand-in on stdio (same daemon)
ts-r-lsp [NODE FLAGS] <path>/tsserver.js [TSSERVER ARGS]
same, when set as VS Code's typescript.tsserver.nodePath
ts-r-lsp status [--root DIR] print the workspace daemon's status as JSON
ts-r-lsp stop [--root DIR] ask the workspace daemon to exit
ts-r-lsp ensure-daemon [--root DIR] start the daemon if needed and print its address
ts-r-lsp daemon --root DIR run the daemon in the foreground
OPTIONS
--root DIR workspace root (default: from initialize, else the cwd)
--memory-budget-mb N ts-r.memoryBudgetMB (default 1500)
--idle-shutdown-minutes N ts-r.idleShutdownMinutes (default 10, 0 = never)
--type-backend ts-r|typescript ts-r.typeBackend (ts-r: our checker, LSP clients only)
--lint auto|on|off ts-r.lint.enable for this front (default auto, which lints)
--lint-only lint diagnostics and code actions only, no type backend
--fallback-tsserver PATH tsserver to use when the project has none
--node PATH node binary for tsserver when node is not on PATH
--name NAME name shown in status and logs
--version, --help
- Editors other than VS Code start
ts-r-lsp --stdioas an ordinary language server. ts-r-lsp statusprints the daemon's state as JSON: clients, projects, memory and each project's parity result.--lint-onlyserves lint diagnostics and code actions only, with no type backend.TS_R_LSP_TYPE_BACKENDoverridests-r.typeBackend.
VS Code
The extension in editors/vscode makes ts-r-lsp VS Code's tsserver, so the built-in TypeScript features keep working on the shared daemon.
| Setting | Default | Effect |
|---|---|---|
ts-r.typeBackend | ts-r | Type backend for LSP clients (VS Code windows use TypeScript) |
ts-r.memoryBudgetMB | 1500 | Restart the backend above this |
ts-r.idleShutdownMinutes | 10 | Stop an idle backend (0 = never) |
ts-r.lint.enable | auto | auto turns lint off when the ESLint extension is enabled |
ts-r.serverPath | Path to ts-r-lsp |
The extension looks for the binary in ts-r.serverPath, then TS_R_LSP_BINARY, then node_modules/@ts-r.dev/lsp-<platform>/bin/, then PATH, then ~/.cargo/bin. See the extension's notes.
Measured
On public projects pinned in bench/repos.toml (Apple M-series shared laptop, one run per tool), with the ts-r backend, against each repo's own tsserver and TypeScript 7's native LSP (7.0.2):
| ts-r | tsserver | TS 7 LSP | |
|---|---|---|---|
tRPC router.ts: first diagnostics | 224 ms | 800 ms | 58 ms |
| tRPC: keystroke to diagnostics, p50 / p95 | 9.4 / 10.5 ms | 24.3 / 29.3 ms | 6.2 / 7.4 ms |
| tRPC: memory, one file / all 154 sources open | 95 / 165 MB | 571 / 757 MB | 477 / 480 MB |
RxJS merge-map.ts: keystroke p50 / p95 | 12.2 / 12.8 ms | 28.6 / 35.1 ms | 13.3 / 17.2 ms |
| RxJS: memory, all 231 sources open | 377 MB | 1,091 MB | 820 MB |
| date-fns: keystroke p50 / p95 | 14.1 / 14.7 ms | timed out | 5.0 / 5.8 ms |
| date-fns: memory, all 1,641 sources open | 232 MB | timed out | 710 MB |
ts-r uses the least memory everywhere, but TypeScript 7's LSP is faster to first diagnostics on all three and per keystroke on tRPC and date-fns (ts-r re-checks each edit on a fresh checker). ts-r also reports a few diagnostics tsc doesn't on the opened RxJS and tRPC files. Large projects haven't been measured yet.
Two editor windows on tRPC (two files open in each) with its TypeScript 6.0.3 as the backend: 698 MB in total, against 838 MB for two plain tsservers and 1,451 MB for VS Code's default per-window setup.
Lint on keystrokes in tRPC's largest server file (22 KB, all 154 sources open): 15.6 / 15.8 ms p50 / p95 with the default 10 ms debounce, against 53.3 / 58.6 ms for the ESLint extension's server. The daemon used 10.6 MB; the ESLint server used 2,296 MB, but it also ran 35 rules of the config that ts-r doesn't run in the editor yet.
Method and commits: editor, editor lint and editor memory.