Auto merge of #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 https://github.com/rust-lang/rust-analyzer/issues/13574
This commit is contained in:
bors 2022-11-18 19:33:38 +00:00
commit 791cb87cdf
6 changed files with 86 additions and 13 deletions

View file

@ -359,14 +359,15 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
.iter() .iter()
.flat_map(|primary_span| { .flat_map(|primary_span| {
let primary_location = primary_location(config, workspace_root, primary_span, snap); let primary_location = primary_location(config, workspace_root, primary_span, snap);
let message = {
let mut message = message.clone(); let mut message = message.clone();
if needs_primary_span_label { if needs_primary_span_label {
if let Some(primary_span_label) = &primary_span.label { if let Some(primary_span_label) = &primary_span.label {
format_to!(message, "\n{}", primary_span_label); format_to!(message, "\n{}", primary_span_label);
} }
} }
message
};
// Each primary diagnostic span may result in multiple LSP diagnostics. // Each primary diagnostic span may result in multiple LSP diagnostics.
let mut diagnostics = Vec::new(); let mut diagnostics = Vec::new();
@ -417,7 +418,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
message: message.clone(), message: message.clone(),
related_information: Some(information_for_additional_diagnostic), related_information: Some(information_for_additional_diagnostic),
tags: if tags.is_empty() { None } else { Some(tags.clone()) }, tags: if tags.is_empty() { None } else { Some(tags.clone()) },
data: None, data: Some(serde_json::json!({ "rendered": rd.rendered })),
}; };
diagnostics.push(MappedRustDiagnostic { diagnostics.push(MappedRustDiagnostic {
url: secondary_location.uri, url: secondary_location.uri,
@ -449,7 +450,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
} }
}, },
tags: if tags.is_empty() { None } else { Some(tags.clone()) }, tags: if tags.is_empty() { None } else { Some(tags.clone()) },
data: None, data: Some(serde_json::json!({ "rendered": rd.rendered })),
}, },
fix: None, fix: None,
}); });
@ -534,7 +535,8 @@ mod tests {
Config::new(workspace_root.to_path_buf(), ClientCapabilities::default()), Config::new(workspace_root.to_path_buf(), ClientCapabilities::default()),
); );
let snap = state.snapshot(); let snap = state.snapshot();
let actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap); let mut actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap);
actual.iter_mut().for_each(|diag| diag.diagnostic.data = None);
expect.assert_debug_eq(&actual) expect.assert_debug_eq(&actual)
} }

View file

@ -396,6 +396,11 @@
"default": true, "default": true,
"type": "boolean" "type": "boolean"
}, },
"rust-analyzer.diagnostics.previewRustcOutput": {
"markdownDescription": "Whether to show the main part of the rendered rustc output of a diagnostic message.",
"default": false,
"type": "boolean"
},
"$generated-start": {}, "$generated-start": {},
"rust-analyzer.assist.emitMustUse": { "rust-analyzer.assist.emitMustUse": {
"markdownDescription": "Whether to insert #[must_use] when generating `as_` methods\nfor enum variants.", "markdownDescription": "Whether to insert #[must_use] when generating `as_` methods\nfor enum variants.",

View file

@ -4,7 +4,7 @@ import * as ra from "../src/lsp_ext";
import * as Is from "vscode-languageclient/lib/common/utils/is"; import * as Is from "vscode-languageclient/lib/common/utils/is";
import { assert } from "./util"; import { assert } from "./util";
import { WorkspaceEdit } from "vscode"; import { WorkspaceEdit } from "vscode";
import { substituteVSCodeVariables } from "./config"; import { Config, substituteVSCodeVariables } from "./config";
import { randomUUID } from "crypto"; import { randomUUID } from "crypto";
export interface Env { export interface Env {
@ -66,7 +66,8 @@ export async function createClient(
traceOutputChannel: vscode.OutputChannel, traceOutputChannel: vscode.OutputChannel,
outputChannel: vscode.OutputChannel, outputChannel: vscode.OutputChannel,
initializationOptions: vscode.WorkspaceConfiguration, initializationOptions: vscode.WorkspaceConfiguration,
serverOptions: lc.ServerOptions serverOptions: lc.ServerOptions,
config: Config
): Promise<lc.LanguageClient> { ): Promise<lc.LanguageClient> {
const clientOptions: lc.LanguageClientOptions = { const clientOptions: lc.LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "rust" }], documentSelector: [{ scheme: "file", language: "rust" }],
@ -99,6 +100,43 @@ export async function createClient(
} }
}, },
}, },
async handleDiagnostics(
uri: vscode.Uri,
diagnostics: vscode.Diagnostic[],
next: lc.HandleDiagnosticsSignature
) {
const preview = config.previewRustcOutput;
diagnostics.forEach((diag, idx) => {
// Abuse the fact that VSCode leaks the LSP diagnostics data field through the
// Diagnostic class, if they ever break this we are out of luck and have to go
// back to the worst diagnostics experience ever:)
// We encode the rendered output of a rustc diagnostic in the rendered field of
// the data payload of the lsp diagnostic. If that field exists, overwrite the
// diagnostic code such that clicking it opens the diagnostic in a readonly
// text editor for easy inspection
const rendered = (diag as unknown as { data?: { rendered?: string } }).data
?.rendered;
if (rendered) {
if (preview) {
const index = rendered.match(/^(note|help):/m)?.index || 0;
diag.message = rendered
.substring(0, index)
.replace(/^ -->[^\n]+\n/m, "");
}
diag.code = {
target: vscode.Uri.from({
scheme: "rust-analyzer-diagnostics-view",
path: "/diagnostic message",
fragment: uri.toString(),
query: idx.toString(),
}),
value: "Click for full compiler diagnostic",
};
}
});
return next(uri, diagnostics);
},
async provideHover( async provideHover(
document: vscode.TextDocument, document: vscode.TextDocument,
position: vscode.Position, position: vscode.Position,

View file

@ -238,6 +238,9 @@ export class Config {
gotoTypeDef: this.get<boolean>("hover.actions.gotoTypeDef.enable"), gotoTypeDef: this.get<boolean>("hover.actions.gotoTypeDef.enable"),
}; };
} }
get previewRustcOutput() {
return this.get<boolean>("diagnostics.previewRustcOutput");
}
} }
const VarRegex = new RegExp(/\$\{(.+?)\}/g); const VarRegex = new RegExp(/\$\{(.+?)\}/g);

View file

@ -179,7 +179,8 @@ export class Ctx {
this.traceOutputChannel, this.traceOutputChannel,
this.outputChannel, this.outputChannel,
initializationOptions, initializationOptions,
serverOptions serverOptions,
this.config
); );
this.pushClientCleanup( this.pushClientCleanup(
this._client.onNotification(ra.serverStatus, (params) => this._client.onNotification(ra.serverStatus, (params) =>

View file

@ -48,6 +48,30 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
ctx.pushExtCleanup(activateTaskProvider(ctx.config)); ctx.pushExtCleanup(activateTaskProvider(ctx.config));
} }
ctx.pushExtCleanup(
vscode.workspace.registerTextDocumentContentProvider(
"rust-analyzer-diagnostics-view",
new (class implements vscode.TextDocumentContentProvider {
async provideTextDocumentContent(uri: vscode.Uri): Promise<string> {
const diags = ctx.client?.diagnostics?.get(
vscode.Uri.parse(uri.fragment, true)
);
if (!diags) {
return "Unable to find original rustc diagnostic";
}
const diag = diags[parseInt(uri.query)];
if (!diag) {
return "Unable to find original rustc diagnostic";
}
const rendered = (diag as unknown as { data?: { rendered?: string } }).data
?.rendered;
return rendered ?? "Unable to find original rustc diagnostic";
}
})()
)
);
vscode.workspace.onDidChangeWorkspaceFolders( vscode.workspace.onDidChangeWorkspaceFolders(
async (_) => ctx.onWorkspaceFolderChanges(), async (_) => ctx.onWorkspaceFolderChanges(),
null, null,