Skip to content

Commit 791cb87

Browse files
committed
Auto merge of rust-lang#13633 - Veykril:vscode-full-diagnostics, r=Veykril
feat: Allow viewing the full compiler diagnostic in a readonly textview ![Code_y1qrash9gg](https://user-images.githubusercontent.com/3757771/202780459-f751f65d-2b1b-4dc3-9685-100d65ebf6a0.gif) Also adds a VSCode only config that replaces the split diagnostic message with the first relevant part of the diagnostic output ![Code_7k4qsMkx5e](https://user-images.githubusercontent.com/3757771/202780346-cf9137d9-eb77-46b7-aed6-c73a2e41e1c7.png) This only affects diagnostics generated by primary spans and has no effect on other clients than VSCode. Fixes rust-lang/rust-analyzer#13574
2 parents bee27eb + 8452844 commit 791cb87

File tree

6 files changed

+86
-13
lines changed

6 files changed

+86
-13
lines changed

crates/rust-analyzer/src/diagnostics/to_proto.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -359,14 +359,15 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
359359
.iter()
360360
.flat_map(|primary_span| {
361361
let primary_location = primary_location(config, workspace_root, primary_span, snap);
362-
363-
let mut message = message.clone();
364-
if needs_primary_span_label {
365-
if let Some(primary_span_label) = &primary_span.label {
366-
format_to!(message, "\n{}", primary_span_label);
362+
let message = {
363+
let mut message = message.clone();
364+
if needs_primary_span_label {
365+
if let Some(primary_span_label) = &primary_span.label {
366+
format_to!(message, "\n{}", primary_span_label);
367+
}
367368
}
368-
}
369-
369+
message
370+
};
370371
// Each primary diagnostic span may result in multiple LSP diagnostics.
371372
let mut diagnostics = Vec::new();
372373

@@ -417,7 +418,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
417418
message: message.clone(),
418419
related_information: Some(information_for_additional_diagnostic),
419420
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
420-
data: None,
421+
data: Some(serde_json::json!({ "rendered": rd.rendered })),
421422
};
422423
diagnostics.push(MappedRustDiagnostic {
423424
url: secondary_location.uri,
@@ -449,7 +450,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
449450
}
450451
},
451452
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
452-
data: None,
453+
data: Some(serde_json::json!({ "rendered": rd.rendered })),
453454
},
454455
fix: None,
455456
});
@@ -534,7 +535,8 @@ mod tests {
534535
Config::new(workspace_root.to_path_buf(), ClientCapabilities::default()),
535536
);
536537
let snap = state.snapshot();
537-
let actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap);
538+
let mut actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap);
539+
actual.iter_mut().for_each(|diag| diag.diagnostic.data = None);
538540
expect.assert_debug_eq(&actual)
539541
}
540542

editors/code/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,11 @@
396396
"default": true,
397397
"type": "boolean"
398398
},
399+
"rust-analyzer.diagnostics.previewRustcOutput": {
400+
"markdownDescription": "Whether to show the main part of the rendered rustc output of a diagnostic message.",
401+
"default": false,
402+
"type": "boolean"
403+
},
399404
"$generated-start": {},
400405
"rust-analyzer.assist.emitMustUse": {
401406
"markdownDescription": "Whether to insert #[must_use] when generating `as_` methods\nfor enum variants.",

editors/code/src/client.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as ra from "../src/lsp_ext";
44
import * as Is from "vscode-languageclient/lib/common/utils/is";
55
import { assert } from "./util";
66
import { WorkspaceEdit } from "vscode";
7-
import { substituteVSCodeVariables } from "./config";
7+
import { Config, substituteVSCodeVariables } from "./config";
88
import { randomUUID } from "crypto";
99

1010
export interface Env {
@@ -66,7 +66,8 @@ export async function createClient(
6666
traceOutputChannel: vscode.OutputChannel,
6767
outputChannel: vscode.OutputChannel,
6868
initializationOptions: vscode.WorkspaceConfiguration,
69-
serverOptions: lc.ServerOptions
69+
serverOptions: lc.ServerOptions,
70+
config: Config
7071
): Promise<lc.LanguageClient> {
7172
const clientOptions: lc.LanguageClientOptions = {
7273
documentSelector: [{ scheme: "file", language: "rust" }],
@@ -99,6 +100,43 @@ export async function createClient(
99100
}
100101
},
101102
},
103+
async handleDiagnostics(
104+
uri: vscode.Uri,
105+
diagnostics: vscode.Diagnostic[],
106+
next: lc.HandleDiagnosticsSignature
107+
) {
108+
const preview = config.previewRustcOutput;
109+
diagnostics.forEach((diag, idx) => {
110+
// Abuse the fact that VSCode leaks the LSP diagnostics data field through the
111+
// Diagnostic class, if they ever break this we are out of luck and have to go
112+
// back to the worst diagnostics experience ever:)
113+
114+
// We encode the rendered output of a rustc diagnostic in the rendered field of
115+
// the data payload of the lsp diagnostic. If that field exists, overwrite the
116+
// diagnostic code such that clicking it opens the diagnostic in a readonly
117+
// text editor for easy inspection
118+
const rendered = (diag as unknown as { data?: { rendered?: string } }).data
119+
?.rendered;
120+
if (rendered) {
121+
if (preview) {
122+
const index = rendered.match(/^(note|help):/m)?.index || 0;
123+
diag.message = rendered
124+
.substring(0, index)
125+
.replace(/^ -->[^\n]+\n/m, "");
126+
}
127+
diag.code = {
128+
target: vscode.Uri.from({
129+
scheme: "rust-analyzer-diagnostics-view",
130+
path: "/diagnostic message",
131+
fragment: uri.toString(),
132+
query: idx.toString(),
133+
}),
134+
value: "Click for full compiler diagnostic",
135+
};
136+
}
137+
});
138+
return next(uri, diagnostics);
139+
},
102140
async provideHover(
103141
document: vscode.TextDocument,
104142
position: vscode.Position,

editors/code/src/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ export class Config {
238238
gotoTypeDef: this.get<boolean>("hover.actions.gotoTypeDef.enable"),
239239
};
240240
}
241+
get previewRustcOutput() {
242+
return this.get<boolean>("diagnostics.previewRustcOutput");
243+
}
241244
}
242245

243246
const VarRegex = new RegExp(/\$\{(.+?)\}/g);

editors/code/src/ctx.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ export class Ctx {
179179
this.traceOutputChannel,
180180
this.outputChannel,
181181
initializationOptions,
182-
serverOptions
182+
serverOptions,
183+
this.config
183184
);
184185
this.pushClientCleanup(
185186
this._client.onNotification(ra.serverStatus, (params) =>

editors/code/src/main.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,30 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
4848
ctx.pushExtCleanup(activateTaskProvider(ctx.config));
4949
}
5050

51+
ctx.pushExtCleanup(
52+
vscode.workspace.registerTextDocumentContentProvider(
53+
"rust-analyzer-diagnostics-view",
54+
new (class implements vscode.TextDocumentContentProvider {
55+
async provideTextDocumentContent(uri: vscode.Uri): Promise<string> {
56+
const diags = ctx.client?.diagnostics?.get(
57+
vscode.Uri.parse(uri.fragment, true)
58+
);
59+
if (!diags) {
60+
return "Unable to find original rustc diagnostic";
61+
}
62+
63+
const diag = diags[parseInt(uri.query)];
64+
if (!diag) {
65+
return "Unable to find original rustc diagnostic";
66+
}
67+
const rendered = (diag as unknown as { data?: { rendered?: string } }).data
68+
?.rendered;
69+
return rendered ?? "Unable to find original rustc diagnostic";
70+
}
71+
})()
72+
)
73+
);
74+
5175
vscode.workspace.onDidChangeWorkspaceFolders(
5276
async (_) => ctx.onWorkspaceFolderChanges(),
5377
null,

0 commit comments

Comments
 (0)