Skip to content

Commit

Permalink
Auto merge of #13848 - ian-h-chamberlain:feature/color-compiler-diagn…
Browse files Browse the repository at this point in the history
…ostics, 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)?
  • Loading branch information
bors committed Jan 9, 2023
2 parents f32e20e + 283dfc4 commit 368e0bb
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 31 deletions.
14 changes: 12 additions & 2 deletions crates/flycheck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub enum FlycheckConfig {
features: Vec<String>,
extra_args: Vec<String>,
extra_env: FxHashMap<String, String>,
ansi_color_output: bool,
},
CustomCommand {
command: String,
Expand Down Expand Up @@ -293,12 +294,21 @@ impl FlycheckActor {
extra_args,
features,
extra_env,
ansi_color_output,
} => {
let mut cmd = Command::new(toolchain::cargo());
cmd.arg(command);
cmd.current_dir(&self.root);
cmd.args(["--workspace", "--message-format=json", "--manifest-path"])
.arg(self.root.join("Cargo.toml").as_os_str());
cmd.arg("--workspace");

cmd.arg(if *ansi_color_output {
"--message-format=json-diagnostic-rendered-ansi"
} else {
"--message-format=json"
});

cmd.arg("--manifest-path");
cmd.arg(self.root.join("Cargo.toml").as_os_str());

for target in target_triples {
cmd.args(["--target", target.as_str()]);
Expand Down
10 changes: 9 additions & 1 deletion crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,9 @@ config_data! {
check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option<bool> = "null",
/// Override the command rust-analyzer uses instead of `cargo check` for
/// diagnostics on save. The command is required to output json and
/// should therefore include `--message-format=json` or a similar option.
/// should therefore include `--message-format=json` or a similar option
/// (if your client supports the `colorDiagnosticOutput` experimental
/// capability, you can use `--message-format=json-diagnostic-rendered-ansi`).
///
/// If you're changing this because you're using some tool wrapping
/// Cargo, you might also want to change
Expand Down Expand Up @@ -1006,6 +1008,11 @@ impl Config {
self.experimental("serverStatusNotification")
}

/// Whether the client supports colored output for full diagnostics from `checkOnSave`.
pub fn color_diagnostic_output(&self) -> bool {
self.experimental("colorDiagnosticOutput")
}

pub fn publish_diagnostics(&self) -> bool {
self.data.diagnostics_enable
}
Expand Down Expand Up @@ -1204,6 +1211,7 @@ impl Config {
},
extra_args: self.data.check_extraArgs.clone(),
extra_env: self.check_on_save_extra_env(),
ansi_color_output: self.color_diagnostic_output(),
},
}
}
Expand Down
26 changes: 26 additions & 0 deletions docs/dev/lsp-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -792,3 +792,29 @@ export interface ClientCommandOptions {
commands: string[];
}
```

## Colored Diagnostic Output

**Experimental Client Capability:** `{ "colorDiagnosticOutput": boolean }`

If this capability is set, the "full compiler diagnostics" provided by `checkOnSave`
will include ANSI color and style codes to render the diagnostic in a similar manner
as `cargo`. This is translated into `--message-format=json-diagnostic-rendered-ansi`
when flycheck is run, instead of the default `--message-format=json`.

The full compiler rendered diagnostics are included in the server response
regardless of this capability:

```typescript
// https://microsoft.github.io/language-server-protocol/specifications/specification-current#diagnostic
export interface Diagnostic {
...
data?: {
/**
* The human-readable compiler output as it would be printed to a terminal.
* Includes ANSI color and style codes if the client has set the experimental
* `colorDiagnosticOutput` capability.
*/
rendered?: string;
};
}
4 changes: 3 additions & 1 deletion docs/user/generated_config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ Whether to pass `--no-default-features` to Cargo. Defaults to
--
Override the command rust-analyzer uses instead of `cargo check` for
diagnostics on save. The command is required to output json and
should therefore include `--message-format=json` or a similar option.
should therefore include `--message-format=json` or a similar option
(if your client supports the `colorDiagnosticOutput` experimental
capability, you can use `--message-format=json-diagnostic-rendered-ansi`).

If you're changing this because you're using some tool wrapping
Cargo, you might also want to change
Expand Down
11 changes: 11 additions & 0 deletions editors/code/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"test": "cross-env TEST_VARIABLE=test node ./out/tests/runTests.js"
},
"dependencies": {
"anser": "^2.1.1",
"d3": "^7.6.1",
"d3-graphviz": "^5.0.2",
"vscode-languageclient": "^8.0.2"
Expand Down Expand Up @@ -643,7 +644,7 @@
]
},
"rust-analyzer.check.overrideCommand": {
"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.",
"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.",
"default": null,
"type": [
"null",
Expand Down
18 changes: 11 additions & 7 deletions editors/code/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import * as anser from "anser";
import * as lc from "vscode-languageclient/node";
import * as vscode from "vscode";
import * as ra from "../src/lsp_ext";
import * as Is from "vscode-languageclient/lib/common/utils/is";
import { assert } from "./util";
import * as diagnostics from "./diagnostics";
import { WorkspaceEdit } from "vscode";
import { Config, substituteVSCodeVariables } from "./config";
import { randomUUID } from "crypto";
Expand Down Expand Up @@ -120,12 +122,12 @@ export async function createClient(
},
async handleDiagnostics(
uri: vscode.Uri,
diagnostics: vscode.Diagnostic[],
diagnosticList: vscode.Diagnostic[],
next: lc.HandleDiagnosticsSignature
) {
const preview = config.previewRustcOutput;
const errorCode = config.useRustcErrorCode;
diagnostics.forEach((diag, idx) => {
diagnosticList.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:)
Expand All @@ -138,9 +140,10 @@ export async function createClient(
?.rendered;
if (rendered) {
if (preview) {
const decolorized = anser.ansiToText(rendered);
const index =
rendered.match(/^(note|help):/m)?.index || rendered.length;
diag.message = rendered
decolorized.match(/^(note|help):/m)?.index || rendered.length;
diag.message = decolorized
.substring(0, index)
.replace(/^ -->[^\n]+\n/m, "");
}
Expand All @@ -154,16 +157,16 @@ export async function createClient(
}
diag.code = {
target: vscode.Uri.from({
scheme: "rust-analyzer-diagnostics-view",
path: "/diagnostic message",
scheme: diagnostics.URI_SCHEME,
path: `/diagnostic message [${idx.toString()}]`,
fragment: uri.toString(),
query: idx.toString(),
}),
value: value ?? "Click for full compiler diagnostic",
};
}
});
return next(uri, diagnostics);
return next(uri, diagnosticList);
},
async provideHover(
document: vscode.TextDocument,
Expand Down Expand Up @@ -330,6 +333,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
caps.codeActionGroup = true;
caps.hoverActions = true;
caps.serverStatusNotification = true;
caps.colorDiagnosticOutput = true;
caps.commands = {
commands: [
"rust-analyzer.runSingle",
Expand Down
Loading

0 comments on commit 368e0bb

Please sign in to comment.