Skip to content

editor: Fix diagnostic rendering when semantic tokens set to full#53008

Merged
SomeoneToIgnore merged 4 commits intozed-industries:mainfrom
lingyaochu:semantic_diagnotics
Apr 7, 2026
Merged

editor: Fix diagnostic rendering when semantic tokens set to full#53008
SomeoneToIgnore merged 4 commits intozed-industries:mainfrom
lingyaochu:semantic_diagnotics

Conversation

@lingyaochu
Copy link
Copy Markdown
Contributor

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content is consistent with the UI/UX checklist
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Closes #50212

There are two unreasonable coupling account for this issue, the coupling of use_tree_sitter with languge_aware in

let use_tree_sitter = !snapshot.semantic_tokens_enabled
|| snapshot.use_tree_sitter_for_syntax(rows.start, cx);
let chunks = snapshot.highlighted_chunks(rows.clone(), use_tree_sitter, style);

and the coupling of language_aware with diagnostics in
pub fn chunks<T: ToOffset>(&self, range: Range<T>, language_aware: bool) -> BufferChunks<'_> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut syntax = None;
if language_aware {
syntax = Some(self.get_highlights(range.clone()));
}
// We want to look at diagnostic spans only when iterating over language-annotated chunks.
let diagnostics = language_aware;
BufferChunks::new(self.text.as_rope(), range, syntax, diagnostics, Some(self))
}

Because of these couplings, when the editor stops using Tree-sitter highlighting when "semantic_tokens" set to "full", it also accidentally stops fetching diagnostic information. This is why error and warning underlines disappear.

I’ve fixed this by adding a separate use_tree_sitter parameter to highlighted_chunks. This way, we can keep language_aware true to get the diagnostic data we need, but still decide whether or not to apply Tree-sitter highlights. I chose to fix this at the highlighted_chunks level because I’m worried that changing the logic in the deeper layers of the DisplayMap or Buffer might have too many side effects that are hard to predict. This approach feels like a safer way to solve the problem.

Release Notes:

  • Fixed a bug where diagnostic underlines disappeared when "semantic_tokens" set to "full"
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Apr 2, 2026
@github-actions github-actions Bot added the community champion Issues filed by our amazing community champions! 🫶 label Apr 2, 2026
@zed-community-bot zed-community-bot Bot added the guild Pull requests by someone in Zed Guild. NOTE: the label application is automated via github actions label Apr 2, 2026
@zed-codeowner-coordinator zed-codeowner-coordinator Bot requested review from a team, as-cii and dinocosta and removed request for a team April 2, 2026 16:26
@SomeoneToIgnore SomeoneToIgnore self-assigned this Apr 2, 2026
Copy link
Copy Markdown
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch, thank you.

One question: why skip the most important part?

Image
Copy link
Copy Markdown
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see now, sorry: it's the rendering code that we're hitting here purely, so it's hard to test — would be amazing if we can get it tested somehow still, but not insisting now.

What's confusing though is this bit:

pub fn chunks<T: ToOffset>(&self, range: Range<T>, language_aware: bool) -> BufferChunks<'_> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut syntax = None;
if language_aware {
syntax = Some(self.get_highlights(range.clone()));
}
// We want to look at diagnostic spans only when iterating over language-annotated chunks.
let diagnostics = language_aware;
BufferChunks::new(self.text.as_rope(), range, syntax, diagnostics, Some(self))
}

it seems wasteful to get language highlights for the full case here — which we will do due to the new code, won't we?
Can we rework the code somehow to avoid getting the highlights but do get the diagnostics?

@SomeoneToIgnore SomeoneToIgnore dismissed their stale review April 2, 2026 18:52

Would be good to get tests, if possible.

@lingyaochu
Copy link
Copy Markdown
Contributor Author

lingyaochu commented Apr 3, 2026

it seems wasteful to get language highlights for the full case here — which we will do due to the new code, won't we?
Can we rework the code somehow to avoid getting the highlights but do get the diagnostics?

Very insightful point! Ideally, when using the "full" mode, Tree-sitter highlighting is unnecessary, and skipping it would indeed be a performance win and also make the architecture cleaner.

However, achieving this would require decoupling diagnostics from syntax highlighting at a very low level. The language_aware parameter currently travels through a long chain of layers: highlighted_chunks -> BlockSnapshot -> WrapSnapshot -> TabSnapshot -> FoldSnapshot -> InlaySnapshot -> MultiBufferSnapshot, and finally into Buffer::chunks().

Modifying the signature of chunks() to independently control diagnostics would force changes across all these intermediate snapshots, not to mention other parts of the codebase that depend on them. I also looked into extending the Highlights struct to pass these settings down, but it is also heavily used and wouldn't lead to a much cleaner fix.

I agree the current fix is a bit "dirty". While it correctly breaks the coupling between use_tree_sitter and language_aware at the orchestration level, it leaves the underlying coupling between syntax and diagnostics intact. Properly decoupling them at the bottom would require a full-stack refactor of the snapshot chain. In my view, performing such an invasive change to every core map layer is a high-risk approach for this issue. I chose this level of fix to restore diagnostics safely while avoiding any potential for regressions in the critical rendering path.


One question: why skip the most important part?

I intended to add a GPUI test, but found that I would have to manually call highlighted_chunks to verify the result. Since this PR adds a new parameter to that function, manually passing the "correct" parameter in a test would only be testing my own new logic in isolation. It would feel like a redundant unit test instead of a real integration test that mimics actual system behavior, so I decided not to include it.

@SomeoneToIgnore
Copy link
Copy Markdown
Contributor

In my view, performing such an invasive change to every core map layer is a high-risk approach for this issue.

Yes, that's a 7-level chain but overall it's triggered in a particular, highlighted above level only, to disable the highlights on one level, the rest is just passing the parameter through, so I find it hard to agree with this phrase.

Do I miss something?
If not, let's take some more time and do things properly.

but found that I would have to manually call highlighted_chunks to verify the result

Is it a bad thing?

If we can have a test with both diagnostics and tokens and the full semantic tokens' level that produces different results on main (without diagnostics) and this branch (with the diagnostics) after calling the highlighted_chunks — that is superb.

@lingyaochu lingyaochu force-pushed the semantic_diagnotics branch from c2e08f4 to b47c639 Compare April 4, 2026 03:36
@lingyaochu
Copy link
Copy Markdown
Contributor Author

Truly thanks for the review! The fix now addresses the root cause directly, and I'm pretty satisfied with how it turned out.

About the test I added: it's a bit tricky because it cannot be easily tested against the current main branch. It will fail to compile rather than failing at the assertion, simply because it passes the new LanguageAware parameter which is only introduced in this fix. Hopefully, it can still provide some value in preventing future regressions.

Copy link
Copy Markdown
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you a lot for the fix and the test!

@SomeoneToIgnore SomeoneToIgnore enabled auto-merge (squash) April 7, 2026 13:24
@lingyaochu
Copy link
Copy Markdown
Contributor Author

May I ask why you chose to pass the full struct in every location instead of using From<bool> and .into()? Is it for better semantic readability?

@SomeoneToIgnore
Copy link
Copy Markdown
Contributor

SomeoneToIgnore commented Apr 7, 2026

Yes, it's totally not clear to me by looking at false.into() what does it convert into, "explicit is better than implicit", etc., etc.
I guess even better would be to have a named method(s) for this, but this way works too as it's mostly about test code changes.

To emphasize, the more important fix was to pass false in the chunks method in the new test, to be 100% sure we test what we want.

@lingyaochu
Copy link
Copy Markdown
Contributor Author

Understood. I'm transitioning from Python and still learning the 'Rust way.' Thanks for the explanation, it helps me keep my future contributions idiomatic.

@SomeoneToIgnore SomeoneToIgnore merged commit 3ed1c32 into zed-industries:main Apr 7, 2026
47 of 49 checks passed
@lingyaochu lingyaochu deleted the semantic_diagnotics branch April 7, 2026 14:20
@SomeoneToIgnore
Copy link
Copy Markdown
Contributor

/cherry-pick preview
/cherry-pick stable

@zed-zippy
Copy link
Copy Markdown
Contributor

zed-zippy Bot commented Apr 7, 2026

@zed-zippy
Copy link
Copy Markdown
Contributor

zed-zippy Bot commented Apr 7, 2026

MasoudAlali pushed a commit to MasoudAlali/zed-ide that referenced this pull request Apr 7, 2026
…d-industries#53008)

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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes zed-industries#50212

There are two unreasonable coupling account for this issue, the coupling
of `use_tree_sitter` with `languge_aware` in

https://github.com/zed-industries/zed/blob/7892b932795911516f26f3c1c1c72249ed181ba8/crates/editor/src/element.rs#L3820-L3822
and the coupling of `language_aware` with `diagnostics` in
https://github.com/zed-industries/zed/blob/7892b932795911516f26f3c1c1c72249ed181ba8/crates/language/src/buffer.rs#L3736-L3746

Because of these couplings, when the editor stops using Tree-sitter
highlighting when `"semantic_tokens"` set to `"full"`, it also
accidentally stops fetching diagnostic information. This is why error
and warning underlines disappear.

I’ve fixed this by adding a separate `use_tree_sitter` parameter to
`highlighted_chunks`. This way, we can keep `language_aware` true to get
the diagnostic data we need, but still decide whether or not to apply
Tree-sitter highlights. I chose to fix this at the `highlighted_chunks`
level because I’m worried that changing the logic in the deeper layers
of the DisplayMap or Buffer might have too many side effects that are
hard to predict. This approach feels like a safer way to solve the
problem.

Release Notes:

- Fixed a bug where diagnostic underlines disappeared when
"semantic_tokens" set to "full"

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
piper-of-dawn pushed a commit to piper-of-dawn/zed that referenced this pull request Apr 25, 2026
…d-industries#53008)

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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes zed-industries#50212

There are two unreasonable coupling account for this issue, the coupling
of `use_tree_sitter` with `languge_aware` in

https://github.com/zed-industries/zed/blob/23eaa7d2b30731447088bc0b0318853049c3a6f0/crates/editor/src/element.rs#L3820-L3822
and the coupling of `language_aware` with `diagnostics` in
https://github.com/zed-industries/zed/blob/23eaa7d2b30731447088bc0b0318853049c3a6f0/crates/language/src/buffer.rs#L3736-L3746

Because of these couplings, when the editor stops using Tree-sitter
highlighting when `"semantic_tokens"` set to `"full"`, it also
accidentally stops fetching diagnostic information. This is why error
and warning underlines disappear.

I’ve fixed this by adding a separate `use_tree_sitter` parameter to
`highlighted_chunks`. This way, we can keep `language_aware` true to get
the diagnostic data we need, but still decide whether or not to apply
Tree-sitter highlights. I chose to fix this at the `highlighted_chunks`
level because I’m worried that changing the logic in the deeper layers
of the DisplayMap or Buffer might have too many side effects that are
hard to predict. This approach feels like a safer way to solve the
problem.

Release Notes:

- Fixed a bug where diagnostic underlines disappeared when
"semantic_tokens" set to "full"

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement community champion Issues filed by our amazing community champions! 🫶 guild Pull requests by someone in Zed Guild. NOTE: the label application is automated via github actions

3 participants