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. |
||
|---|---|---|
| .. | ||
| src | ||
| build.rs | ||
| Cargo.toml | ||
| LICENSE-GPL | ||
| README.md | ||
Inspector
This is a tool for inspecting and manipulating rendered elements in Zed. It is only available in debug builds. Use the dev::ToggleInspector action to toggle inspector mode and click on UI elements to inspect them.
Current features
-
Picking of elements via the mouse, with scroll wheel to inspect occluded elements.
-
Temporary manipulation of the selected element.
-
Layout info for
Div. -
Both Rust and JSON-based style manipulation of
Divstyle. The rust style editor only supports argumentlessStyledandStyledExtmethod calls. -
Navigation to code that constructed the element.
Known bugs
JSON style editor undo history doesn't get reset
The JSON style editor appends to its undo stack on every change of the active inspected element.
I attempted to fix it by creating a new buffer and setting the buffer associated with the json_style_buffer entity. Unfortunately this doesn't work because the language server uses the version: clock::Global to figure out the changes, so would need some way to start the new buffer's text at that version.
json_style_buffer.update(cx, |json_style_buffer, cx| {
let language = json_style_buffer.language().cloned();
let file = json_style_buffer.file().cloned();
*json_style_buffer = Buffer::local("", cx);
json_style_buffer.set_language(language, cx);
if let Some(file) = file {
json_style_buffer.file_updated(file, cx);
}
});
Future features
-
Action and keybinding for entering pick mode.
-
Ability to highlight current element after it's been picked.
-
Info and manipulation of element types other than
Div. -
Indicate when the picked element has disappeared.
-
To inspect elements that disappear, it would be helpful to be able to pause the UI.
-
Hierarchy view?
Methods that take arguments in Rust style editor
Could use TreeSitter to parse out the fluent style method chain and arguments. Tricky part of this is completions - ideally the Rust Analyzer already being used by the developer's Zed would be used.
Edit original code in Rust style editor
Two approaches:
-
Open an excerpt of the original file.
-
Communicate with the Zed process that has the repo open - it would send the code for the element. This seems like a lot of work, but would be very nice for rapid development, and it would allow use of rust analyzer.
With both approaches, would need to record the buffer version and use that when referring to source locations, since editing elements can cause code layout shift.
Source location UI improvements
-
Mode to navigate to source code on every element change while picking.
-
Tracking of more source locations - currently the source location is often in a ui component. Ideally this would have a way for the components to indicate that they are probably not the source location the user is looking for.
-
Could have
InspectorElementIdbeVec<(ElementId, Option<Location>)>, but if there are multiple code paths that construct the same element this would cause them to be considered different. -
Probably better to have a separate
Vec<Option<Location>>that uses the same indices asGlobalElementId.
-
Persistent modification
Currently, element modifications disappear when picker mode is started. Handling this well is tricky. Potential features:
-
Support modifying multiple elements at once. This requires a way to specify which elements are modified - possibly wildcards in a match of the
InspectorElementIdpath. This might default to ignoring all numeric parts and just matching on the names. -
Show a list of active modifications in the UI.
-
Support for modifications being partial overrides instead of snapshots. A trickiness here is that multiple modifications may apply to the same element.
-
The code should probably distinguish the data that is provided by the element and the modifications from the inspector. Currently these are conflated in element states.
If support is added for editing original code, then the logical selector in this case would be just matches of the source path.
Code cleanups
Consider removing special side pane rendering
Currently the inspector has special rendering in the UI, but maybe it could just be a workspace item.
Pull more inspector logic out of GPUI
Currently crates/gpui/inspector.rs and crates/inspector_ui/inspector.rs are quite entangled. It seems cleaner to pull as much logic a possible out of GPUI.
Cleaner lifecycle for inspector state viewers / editors
Currently element state inspectors are just called on render. Ideally instead they would be implementors of some trait like:
trait StateInspector: Render {
fn new(cx: &mut App) -> Task<Self>;
fn element_changed(inspector_id: &InspectorElementId, window: &mut Window, cx: &mut App);
}
See div_inspector.rs - it needs to initialize itself, keep track of its own loading state, and keep track of the last inspected ID in its render function.