Skip to content

Commit 368e0bb

Browse files
committed
Auto merge of #13848 - ian-h-chamberlain:feature/color-compiler-diagnostics, r=ian-h-chamberlain
Colorize `cargo check` diagnostics in VSCode via text decorations Fixes #13648 ![colored-rustc-diagnostics](https://user-images.githubusercontent.com/11131775/209479884-10eef8ca-37b4-4aae-88f7-3591ac01b25e.gif) Use ANSI control characters to display text decorations matching the VScode terminal theme, and strip them out when providing text content for rustc diagnostics. This adds the small [`anser`](https://www.npmjs.com/package/anser) library (MIT license, no dependencies) to parse the control codes, and it also supports HTML output so it should be fairly easy to switch to a rendered HTML/webview implementation in the future I also updated the default `cargo check` command to use the rendered ANSI diagnostics, although I'm not sure if it makes sense to put this kind of thing behind a feature flag, or whether it might have any issues on Windows (as I believe ANSI codes are not used for colorization there)?
2 parents f32e20e + 283dfc4 commit 368e0bb

File tree

9 files changed

+328
-31
lines changed

9 files changed

+328
-31
lines changed

crates/flycheck/src/lib.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub enum FlycheckConfig {
4747
features: Vec<String>,
4848
extra_args: Vec<String>,
4949
extra_env: FxHashMap<String, String>,
50+
ansi_color_output: bool,
5051
},
5152
CustomCommand {
5253
command: String,
@@ -293,12 +294,21 @@ impl FlycheckActor {
293294
extra_args,
294295
features,
295296
extra_env,
297+
ansi_color_output,
296298
} => {
297299
let mut cmd = Command::new(toolchain::cargo());
298300
cmd.arg(command);
299301
cmd.current_dir(&self.root);
300-
cmd.args(["--workspace", "--message-format=json", "--manifest-path"])
301-
.arg(self.root.join("Cargo.toml").as_os_str());
302+
cmd.arg("--workspace");
303+
304+
cmd.arg(if *ansi_color_output {
305+
"--message-format=json-diagnostic-rendered-ansi"
306+
} else {
307+
"--message-format=json"
308+
});
309+
310+
cmd.arg("--manifest-path");
311+
cmd.arg(self.root.join("Cargo.toml").as_os_str());
302312

303313
for target in target_triples {
304314
cmd.args(["--target", target.as_str()]);

crates/rust-analyzer/src/config.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,9 @@ config_data! {
160160
check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option<bool> = "null",
161161
/// Override the command rust-analyzer uses instead of `cargo check` for
162162
/// diagnostics on save. The command is required to output json and
163-
/// should therefore include `--message-format=json` or a similar option.
163+
/// should therefore include `--message-format=json` or a similar option
164+
/// (if your client supports the `colorDiagnosticOutput` experimental
165+
/// capability, you can use `--message-format=json-diagnostic-rendered-ansi`).
164166
///
165167
/// If you're changing this because you're using some tool wrapping
166168
/// Cargo, you might also want to change
@@ -1006,6 +1008,11 @@ impl Config {
10061008
self.experimental("serverStatusNotification")
10071009
}
10081010

1011+
/// Whether the client supports colored output for full diagnostics from `checkOnSave`.
1012+
pub fn color_diagnostic_output(&self) -> bool {
1013+
self.experimental("colorDiagnosticOutput")
1014+
}
1015+
10091016
pub fn publish_diagnostics(&self) -> bool {
10101017
self.data.diagnostics_enable
10111018
}
@@ -1204,6 +1211,7 @@ impl Config {
12041211
},
12051212
extra_args: self.data.check_extraArgs.clone(),
12061213
extra_env: self.check_on_save_extra_env(),
1214+
ansi_color_output: self.color_diagnostic_output(),
12071215
},
12081216
}
12091217
}

docs/dev/lsp-extensions.md

+26
Original file line numberDiff line numberDiff line change
@@ -792,3 +792,29 @@ export interface ClientCommandOptions {
792792
commands: string[];
793793
}
794794
```
795+
796+
## Colored Diagnostic Output
797+
798+
**Experimental Client Capability:** `{ "colorDiagnosticOutput": boolean }`
799+
800+
If this capability is set, the "full compiler diagnostics" provided by `checkOnSave`
801+
will include ANSI color and style codes to render the diagnostic in a similar manner
802+
as `cargo`. This is translated into `--message-format=json-diagnostic-rendered-ansi`
803+
when flycheck is run, instead of the default `--message-format=json`.
804+
805+
The full compiler rendered diagnostics are included in the server response
806+
regardless of this capability:
807+
808+
```typescript
809+
// https://microsoft.github.io/language-server-protocol/specifications/specification-current#diagnostic
810+
export interface Diagnostic {
811+
...
812+
data?: {
813+
/**
814+
* The human-readable compiler output as it would be printed to a terminal.
815+
* Includes ANSI color and style codes if the client has set the experimental
816+
* `colorDiagnosticOutput` capability.
817+
*/
818+
rendered?: string;
819+
};
820+
}

docs/user/generated_config.adoc

+3-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,9 @@ Whether to pass `--no-default-features` to Cargo. Defaults to
173173
--
174174
Override the command rust-analyzer uses instead of `cargo check` for
175175
diagnostics on save. The command is required to output json and
176-
should therefore include `--message-format=json` or a similar option.
176+
should therefore include `--message-format=json` or a similar option
177+
(if your client supports the `colorDiagnosticOutput` experimental
178+
capability, you can use `--message-format=json-diagnostic-rendered-ansi`).
177179

178180
If you're changing this because you're using some tool wrapping
179181
Cargo, you might also want to change

editors/code/package-lock.json

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editors/code/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"test": "cross-env TEST_VARIABLE=test node ./out/tests/runTests.js"
3636
},
3737
"dependencies": {
38+
"anser": "^2.1.1",
3839
"d3": "^7.6.1",
3940
"d3-graphviz": "^5.0.2",
4041
"vscode-languageclient": "^8.0.2"
@@ -643,7 +644,7 @@
643644
]
644645
},
645646
"rust-analyzer.check.overrideCommand": {
646-
"markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option.\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects, this command is invoked for\neach of them, with the working directory being the project root\n(i.e., the folder containing the `Cargo.toml`).\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
647+
"markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects, this command is invoked for\neach of them, with the working directory being the project root\n(i.e., the folder containing the `Cargo.toml`).\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
647648
"default": null,
648649
"type": [
649650
"null",

editors/code/src/client.ts

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import * as anser from "anser";
12
import * as lc from "vscode-languageclient/node";
23
import * as vscode from "vscode";
34
import * as ra from "../src/lsp_ext";
45
import * as Is from "vscode-languageclient/lib/common/utils/is";
56
import { assert } from "./util";
7+
import * as diagnostics from "./diagnostics";
68
import { WorkspaceEdit } from "vscode";
79
import { Config, substituteVSCodeVariables } from "./config";
810
import { randomUUID } from "crypto";
@@ -120,12 +122,12 @@ export async function createClient(
120122
},
121123
async handleDiagnostics(
122124
uri: vscode.Uri,
123-
diagnostics: vscode.Diagnostic[],
125+
diagnosticList: vscode.Diagnostic[],
124126
next: lc.HandleDiagnosticsSignature
125127
) {
126128
const preview = config.previewRustcOutput;
127129
const errorCode = config.useRustcErrorCode;
128-
diagnostics.forEach((diag, idx) => {
130+
diagnosticList.forEach((diag, idx) => {
129131
// Abuse the fact that VSCode leaks the LSP diagnostics data field through the
130132
// Diagnostic class, if they ever break this we are out of luck and have to go
131133
// back to the worst diagnostics experience ever:)
@@ -138,9 +140,10 @@ export async function createClient(
138140
?.rendered;
139141
if (rendered) {
140142
if (preview) {
143+
const decolorized = anser.ansiToText(rendered);
141144
const index =
142-
rendered.match(/^(note|help):/m)?.index || rendered.length;
143-
diag.message = rendered
145+
decolorized.match(/^(note|help):/m)?.index || rendered.length;
146+
diag.message = decolorized
144147
.substring(0, index)
145148
.replace(/^ -->[^\n]+\n/m, "");
146149
}
@@ -154,16 +157,16 @@ export async function createClient(
154157
}
155158
diag.code = {
156159
target: vscode.Uri.from({
157-
scheme: "rust-analyzer-diagnostics-view",
158-
path: "/diagnostic message",
160+
scheme: diagnostics.URI_SCHEME,
161+
path: `/diagnostic message [${idx.toString()}]`,
159162
fragment: uri.toString(),
160163
query: idx.toString(),
161164
}),
162165
value: value ?? "Click for full compiler diagnostic",
163166
};
164167
}
165168
});
166-
return next(uri, diagnostics);
169+
return next(uri, diagnosticList);
167170
},
168171
async provideHover(
169172
document: vscode.TextDocument,
@@ -330,6 +333,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
330333
caps.codeActionGroup = true;
331334
caps.hoverActions = true;
332335
caps.serverStatusNotification = true;
336+
caps.colorDiagnosticOutput = true;
333337
caps.commands = {
334338
commands: [
335339
"rust-analyzer.runSingle",

0 commit comments

Comments
 (0)