zed/crates/collab
Buyun Xu 3624a5bfda
project: Anchor diagnostic related information that points into the buffer (#62805)
Closes #62796. Follow-up to #62110.

# Objective

The range of a diagnostic entry is anchored when it is ingested, so it
follows edits. The ranges in the related information kept on the
diagnostic were the ones the server published. A code action request
carried both and reported the same note at two different lines: the
entry Zed flattened that note into had followed the edit, the related
information had not.

The distance does not correct itself either. When diagnostics are merged
rather than replaced, the existing entries are re-collected from their
anchors while the payload is cloned as it is, so the two positions drift
further apart with every edit that passes.

`mlir-lsp-server` shows what this costs. `MLIRTextFile::getCodeActions`
takes the line number out of `relatedInformation`, and
`getCodeActionForDiagnostic` resolves it against its own current
document, reading that line to copy its indentation before inserting the
`expected-note` check.

## Solution

The related information moves from `Diagnostic` onto
`DiagnosticEntry<T>`, next to the range it belongs with. The locations
of the diagnostic's own file are then in the same coordinate space as
that range: anchors inside the buffer, points in the worktree's store.
`Diagnostic` carries no coordinates again, so nothing that is only
meaningful inside one buffer travels with a payload that outlives it.

Every transition goes through `DiagnosticEntry::map_coordinates`: the
unsaved-edit adjustment and the clipping when diagnostics are ingested,
the anchoring in `DiagnosticSet::new`, and the conversion back to points
in `merge_diagnostic_entries`. Only the diagnostic's own range is
widened when it is empty, since that is for how it is rendered, while
the related locations are reported back as the server framed them.

Locations in another file have nothing here to anchor to and are kept as
published, which is also what `mlir-lsp-server` expects, since it skips
them.

`DiagnosticEntryRef` is left alone. It is what the rendering path
iterates, down to the scrollbar markers that walk every diagnostic of
the buffer on each frame, so nothing there converts or allocates. The
entries are read through `diagnostic_entries_in_range` where the request
is built, and `diagnostics_in_range` is now implemented on top of it.

The field is an `Option`, as an empty `Arc<[_]>` still allocates and
most diagnostics carry no related information.

`data` has the same staleness and cannot be anchored, as it is opaque.

## Commits

The third commit is mechanical: it introduces `DiagnosticEntry::new` and
rewrites the literals at its call sites, so that the last commit holds
only the change of behaviour.

## Testing

Three tests, all failing before this change. The first two are added as
separate commits, so that they can be run against `main`:

- `test_code_actions_related_information_follows_edits` edits above the
note and requests code actions, where the two positions for it used to
disagree.
- `test_code_actions_related_information_drifts_across_merges` edits and
pulls diagnostics twice, where the distance used to be every line
inserted since the diagnostic was published rather than the last edit
alone.
- `test_code_actions_related_information_of_disk_based_diagnostics`
publishes a diagnostic computed against the file on disk while the
buffer holds an unsaved edit, covering the adjustment the two positions
share.

- `cargo test -p project`
- `cargo test -p language -p diagnostics -p editor`
- `cargo fmt --all -- --check`
- `./script/clippy -p project -p language`

## 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 adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

---

Release Notes:

- Fixed language servers receiving outdated positions for the related
information of a diagnostic when code actions are requested.
2026-08-18 15:06:02 +00:00
..
k8s collab: Route get_users_by_ids through Cloud (#56105) 2026-05-08 13:34:46 +00:00
migrations Make file scanner less eager in non-git-tracked directory trees (#62583) 2026-08-13 17:07:06 +00:00
migrations.sqlite Make file scanner less eager in non-git-tracked directory trees (#62583) 2026-08-13 17:07:06 +00:00
seed
src Support blaming parent revisions (#62614) 2026-08-14 20:24:13 +00:00
tests/integration project: Anchor diagnostic related information that points into the buffer (#62805) 2026-08-18 15:06:02 +00:00
.env.toml collab: Remove seeding infrastructure (#56562) 2026-05-13 15:44:55 +00:00
Cargo.toml collab: Fix multiworkspace location out of sync bugs (#61598) 2026-07-25 03:39:45 +00:00
LICENSE-GPL Relicense Zed source code under GPL (#57948) 2026-05-28 20:19:17 +00:00
README.md collab: Don't run migrations on startup (#44430) 2025-12-08 21:46:52 +00:00
seed.default.json Add more admin to seed script (#34220) 2025-07-10 16:54:00 +00:00

Zed Server

This crate is what we run at https://collab.zed.dev.

It contains our back-end logic for collaboration, to which we connect from the Zed client via a websocket after authenticating via https://zed.dev, which is a separate repo running on Vercel.

Local Development

Database setup

Before you can run the collab server locally, you'll need to set up a zed Postgres database. Follow the steps sequentially:

  1. Ensure you have postgres installed. If not, install with brew install postgresql@15.
  2. Follow the steps on Brew's formula and verify your $PATH contains /opt/homebrew/opt/postgresql@15/bin.
  3. If you hadn't done it before, create the postgres user with createuser -s postgres.
  4. You are now ready to run the bootstrap script:
script/bootstrap

This script will set up the zed Postgres database, and populate it with some users. It requires internet access, because it fetches some users from the GitHub API.

The script will create several admin users, who you'll sign in as by default when developing locally. The GitHub logins for the default users are specified in the seed.default.json file.

To use a different set of admin users, create crates/collab/seed.json.

{
  "admins": ["yourgithubhere"],
  "channels": ["zed"]
}

Testing collaborative features locally

In one terminal, run Zed's collaboration server and the livekit dev server:

foreman start

In a second terminal, run two or more instances of Zed.

script/zed-local -2

This script starts one to four instances of Zed, depending on the -2, -3 or -4 flags. Each instance will be connected to the local collab server, signed in as a different user from seed.json or seed.default.json.

Deployment

We run two instances of collab:

Both of these run on the Kubernetes cluster hosted in Digital Ocean.

Deployment is triggered by pushing to the collab-staging (or collab-production) tag in GitHub. The best way to do this is:

  • ./script/deploy-collab staging
  • ./script/deploy-collab production

You can tell what is currently deployed with ./script/what-is-deployed.