[ty] Implement rust-analyzer's "Click for full compiler diagnostic" feature#26269
Conversation
Allow clients to advertise an experimental fullDiagnosticOutput capability. For clients that advertise this capability, include both a plain-text rendering and the original diagnostic identifier in Diagnostic.data; otherwise retain the existing fix-only payload and avoid the rendering cost. Model full-output and fix-only payloads as separate untagged variants so Rust represents the required field combinations while preserving the existing wire format. Keep the identifier in Diagnostic.data because clients may replace Diagnostic.code with a link to the rendered report. Teach code actions to recover the identifier and optional fix from Diagnostic.data so replacing the visible diagnostic code with a link does not disable lazy or eager quick fixes. Rendered reports embed source snippets whereas raw diagnostics do not. Include the source text of every file referenced by diagnostic annotations in pull-diagnostic result IDs for opted-in clients, deduplicating files before hashing. This conservatively invalidates cached reports after source-only edits without rendering every diagnostic merely to compute cache keys. To preserve existing behavior, continue to omit result IDs from empty workspace reports. Document the experimental protocol and add tests covering push and pull output, source-sensitive cache invalidation, and code actions after the visible diagnostic code has been replaced with a link.
|
(Nice, excited to review this tomorrow unless someone else gets to it :)) |
|
|
||
| `ty server` implements the Language Server Protocol for ty's editor integrations. | ||
|
|
||
| ## LSP extensions |
There was a problem hiding this comment.
I think we should add this in https://docs.astral.sh/ty/features/language-server/ to make it more visible.
There was a problem hiding this comment.
Sounds good. That'll have to be a followup PR. I'll remove nearly everything from the README here and just add a link to that page, saying that extensions to the LSP are documented there.
| let new_result_id = Diagnostics::result_id_from_hash(&[], &[]); | ||
|
|
||
| let report = match new_result_id { | ||
| Some(new_id) if new_id == previous_result_id => { |
There was a problem hiding this comment.
Oh, good catch! I was confused to see why is this code being deleted, then I realized that the result_id_from_hash call above is a no-op as it always returns None
|
|
||
| `ty server` implements the Language Server Protocol for ty's editor integrations. | ||
|
|
||
| Extensions to the protocol are documented in the [ty language server documentation](https://docs.astral.sh/ty/features/language-server/). |
There was a problem hiding this comment.
(I'll add docs there as a followup after this lands, there obviously aren't any there right now)
Apply path-separator filtering in the shared end-to-end test context so every server snapshot uses stable Unix-style separators. This avoids platform-only failures when rendered diagnostics include relative Windows paths.
## Summary This is the ty-vscode companion PR to astral-sh/ruff#26269. See the description on that PR for a full breakdown of the feature and a demo video of it in action. This was all written and checked by codex. I'm unfamiliar with both TypeScript and writing VSCode extensions, so I didn't even try to review it :-) ## Implementation details (Codex-authored) Advertise the experimental fullDiagnosticOutput capability and replace the code of each diagnostic for which the server provides full output with a “Click for full diagnostic” link. Back each link with a virtual text document containing the server-rendered plain text. Append any existing rule-documentation URL to that virtual document so replacing the diagnostic code with a link does not hide access to the rule documentation. Support push, document-pull, and workspace-pull diagnostics. Store rendered contents in the virtual-document provider instead of reading the current diagnostic array by index because pull reports can be rejected by vscode-languageclient result-ID precedence logic. Tag each converted pull report with an extension-local ID, and commit its rendered contents to the cache only after transformed diagnostics for that same ID appear in the VS Code diagnostic collection. This prevents discarded pull results from overwriting current content while retaining the simple index-based virtual URI scheme. Handle workspace pulls directly in middleware because vscode-languageclient 9 captures its original reporter in a closure; replacing the reporter therefore cannot decorate reports before the library applies its result-ID precedence check. Serialize conversion of workspace-pull partial results, discard results from superseded or cancelled requests, and feed converted reports through the original reporter to preserve the built-in precedence behavior. Clear cached virtual documents on server restart and extension disposal. Add a link from the extension contributor documentation to the server protocol documentation. ## Test Plan See astral-sh/ruff#26269
…Protocol (#3855) ## Summary This documents the experimental LSP extension added in astral-sh/ruff#26269 / astral-sh/ty-vscode#462
## Context This builds on the original **Click for full diagnostic** feature implemented in [astral-sh/ruff#26269](astral-sh/ruff#26269) and [#462](#462). The colored rendering follows the approach established by [rust-lang/rust-analyzer#13848](rust-lang/rust-analyzer#13848). ## Summary - advertise support for colored full diagnostic output - strip ANSI sequences from the virtual document and recreate their styles with VS Code decorations - support named terminal colors, 256-color palette entries, truecolor, and text decorations ## Companion PRs - [astral-sh/ruff#26384](astral-sh/ruff#26384) - [astral-sh/ty#3858](astral-sh/ty#3858) ## Test plan https://github.com/user-attachments/assets/36638263-fe48-4ba7-8abe-95db353f1257
## Context This builds on the original **Click for full diagnostic** feature implemented in [astral-sh/ruff#26269](astral-sh/ruff#26269) and [astral-sh/ty-vscode#462](astral-sh/ty-vscode#462). The colored rendering follows the approach established by [rust-lang/rust-analyzer#13848](rust-lang/rust-analyzer#13848). ## Summary Explain that `fullDiagnosticOutput` can include ANSI colour codes and clients are expected to be able to handle this ## Companion PRs - [astral-sh/ruff#26384](astral-sh/ruff#26384) - [astral-sh/ty-vscode#463](astral-sh/ty-vscode#463)
## Context This builds on the original **Click for full diagnostic** feature implemented in [#26269](#26269) and [astral-sh/ty-vscode#462](astral-sh/ty-vscode#462). The colored rendering follows the approach established by [rust-lang/rust-analyzer#13848](rust-lang/rust-analyzer#13848). ## Summary - render full ty diagnostics with ANSI color and styling when the client opts in - negotiate color support separately from `fullDiagnosticOutput` for rollout compatibility - disable terminal hyperlinks in LSP output and cover the behavior under forced hyperlink support ## Companion PRs - [astral-sh/ty-vscode#463](astral-sh/ty-vscode#463) - [astral-sh/ty#3858](astral-sh/ty#3858) ## Test plan https://github.com/user-attachments/assets/dc7e7538-ae2b-4870-9e9b-f59943ee804a
Summary
Fixes astral-sh/ty#1731. See astral-sh/ty-vscode#462 for the companion ty-vscode PR.
This PR implements a parallel to rust-analyzer's "Click for full compiler diagnostic feature" in its VSCode extension. Here's a video of rust-analyzer's feature in action in VSCode:
Screen.Recording.2025-12-02.at.19.55.13.mov
The feature is implemented by allowing LSP clients to advertise an experimental
fullDiagnosticOutputcapability. For clients that advertise this capability, we include both a plain-text rendering and the original diagnostic identifier inDiagnostic.data; otherwise, we retain the existing fix-only payload and avoid the rendering cost.The one (significant) part of rust-analyzer's feature that is not implemented in this PR is full color rendering of diagnostics. This is left out of scope for this PR, as it would have led to a significant increase in code, and can be done as a followup.
Prior art in other LSPs
rust-analyzer is the best known LSP that provides this kind of feature. Similar features are also found in Flow and wsgl-analyzer.
Pyrefly has experimental support for Markdown rendering in tooltips, but this is an orthogonal feature. Here's codex's summary of the differences between the two features.
Codex comparison
Full compiler diagnostics and Markdown diagnostic messages
ty’s full-diagnostic feature and Pyrefly’s Markdown diagnostic-message support improve diagnostic presentation at different layers and are largely complementary.
The ty feature follows the model established by rust-analyzer. The language server preserves the ordinary concise
Diagnostic.messagebut attaches a second, substantially richer representation inDiagnostic.data.rendered. This rendering can contain source snippets, annotated spans, notes, and other multiline context that would be unwieldy inside a normal diagnostic hover.ty-vscode detects the additional data and replaces the displayed diagnostic code with a Click for full diagnostic link. Following the link opens the rendered report in a read-only virtual document. This requires bespoke client middleware and a virtual-document provider, but clients that do not understand the extension can safely ignore the additional
dataand continue displaying the standard concise diagnostic.Repurposing
Diagnostic.codeas a link means that ty must preserve the original diagnostic identifier elsewhere. It therefore includesdata.diagnostic_id, which allows the server to recover identifiers such asinvalid-argument-typewhen processing subsequent code-action requests.The protocol is documented in ty’s language-server documentation, while the client-side transformation can be seen in ty-vscode’s diagnostic handling.
rust-analyzer uses essentially the same client-side design. Its VS Code extension reads a compiler rendering from
Diagnostic.data.rendered, changes the diagnostic code into a link, and opens the report through a custom URI. The implementation is in rust-analyzer’s VS Code client, and the protocol is described under Colored Diagnostic Output. One distinction is that rust-analyzer receives these reports from rustc through check-on-save and can request ANSI styling, whereas ty renders its own diagnostics and the current MVP emits plain text.Pyrefly’s Markdown diagnostic-message support has a different purpose. Rather than providing a second, expanded representation, it changes the content type of the existing
Diagnostic.message. When the client advertisestextDocument.diagnostic.markupMessageSupport, Pyrefly sends the message as:This uses the proposed LSP 3.18 extension that permits
Diagnostic.messageto containMarkupContent. A compatible client can render the Markdown directly in its ordinary diagnostic UI without any Pyrefly-specific virtual-document provider. Clients that do not advertise support continue receiving a standard string.Pyrefly currently uses this mechanism primarily to render backtick-delimited identifiers and types as inline
Implementation details
We keep the identifier in
Diagnostic.databecause clients may replaceDiagnostic.codewith a link to the rendered report. We teach code actions to recover the identifier and optional fix fromDiagnostic.dataso replacing the visible diagnostic code with a link does not disable lazy or eager quick fixes.Rendered reports embed source snippets whereas raw diagnostics do not. We therefore include the source text of every file referenced by diagnostic annotations in pull-diagnostic result IDs for opted-in clients, deduplicating files before hashing. This conservatively invalidates cached reports after source-only edits without rendering every diagnostic merely to compute cache keys. To preserve existing behavior, we continue to omit result IDs from empty workspace reports.
In the same way that rust-analyzer does (source code here), we document the experimental protocol explicitly, describing how clients can declare support for it.
Test Plan
Tests have been added covering push and pull output, source-sensitive cache invalidation, and code actions after the visible diagnostic code has been replaced with a link.
And here is a manual test:
Screen.Recording.2026-06-23.at.14.39.07.mov
To reproduce the above manual test, clone https://github.com/AlexWaygood/ty-full-diagnostic-demo and follow the instructions in that repo's README.md.