lsp_button: Fix missing server metadata after restart (#55162)
Restarting an LSP from the picker drops the server's metadata (version,
memory, "View Logs") even though the new server is healthy. Reproduces
with `vtsls`, `eslint`, `rust-analyzer`, and others; `vlsls` doesn't.
The button kept id-keyed state in
[`health_statuses`](https://github.com/zed-industries/zed/blob/main/crates/language_tools/src/lsp_button.rs#L159)
and
[`servers_per_buffer_abs_path`](https://github.com/zed-industries/zed/blob/main/crates/language_tools/src/lsp_button.rs#L161),
but never handled
[`LspStoreEvent::LanguageServerRemoved`](https://github.com/zed-industries/zed/blob/main/crates/project/src/lsp_store.rs#L4066)
— there was even a [stale
TODO](https://github.com/zed-industries/zed/blob/main/crates/language_tools/src/lsp_button.rs#L860)
claiming the event wasn't emitted (it's emitted from [five
sites](https://github.com/zed-industries/zed/blob/main/crates/project/src/lsp_store.rs#L11190)
in `lsp_store`). On restart the dead id lingered next to the new one in
both maps, and rendering picked the wrong entry.
Fix: handle `LanguageServerRemoved` and evict the dead id from both
maps. `binary_statuses` is left alone — it's name-keyed and shared
across restart cycles to drive the "Downloading… → Starting…" UX.
The cleanup is extracted to `LanguageServers::remove_server` so it's
directly unit-testable. Added four tests covering health eviction,
per-buffer eviction with empty-entry pruning, `binary_statuses`
preservation, and the full restart sequence.
[PR #50417](https://github.com/zed-industries/zed/pull/50417) attempted
this earlier with a `LanguageServerAdded` handler. @SomeoneToIgnore
flagged that as the wrong hook ([buffer
registration](https://github.com/zed-industries/zed/blob/main/crates/language_tools/src/lsp_button.rs#L919-L948)
is what populates state, not startup) and asked for tests. Handling
`LanguageServerRemoved` is a cleaner fit — it's the natural counterpart
to registration, and it also fixes the related case where stopping a
server (without restart) leaks state.
## Verification
Reproduced on `main` and verified the fix on this branch with `vtsls`
and `eslint` against a small TypeScript project.
| Before restart | After restart on `main` (bug) | After restart on this
branch (fix) |
|---|---|---|
| <img width="588" alt="vtsls row before restart — version, memory, View
Logs all visible"
src="https://github.com/user-attachments/assets/bdd2610b-cf8f-4ea1-bc45-f363b098c146"
/> | <img width="536" alt="vtsls row after restart on main — metadata
gone"
src="https://github.com/user-attachments/assets/66b3eefd-f801-4bfd-8b80-5f0e20fb3bc6"
/> | <img width="554" alt="vtsls row after restart on fix branch —
metadata persists"
src="https://github.com/user-attachments/assets/51d4df48-1223-4b21-856f-72d4c1eb2a6a"
/> |
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #53627
Release Notes:
- Fixed missing language server metadata in the LSP menu after restart.
</content>
---------
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>