mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
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  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)?
This commit is contained in:
commit
368e0bb32f
@ -47,6 +47,7 @@ pub enum FlycheckConfig {
|
|||||||
features: Vec<String>,
|
features: Vec<String>,
|
||||||
extra_args: Vec<String>,
|
extra_args: Vec<String>,
|
||||||
extra_env: FxHashMap<String, String>,
|
extra_env: FxHashMap<String, String>,
|
||||||
|
ansi_color_output: bool,
|
||||||
},
|
},
|
||||||
CustomCommand {
|
CustomCommand {
|
||||||
command: String,
|
command: String,
|
||||||
@ -293,12 +294,21 @@ impl FlycheckActor {
|
|||||||
extra_args,
|
extra_args,
|
||||||
features,
|
features,
|
||||||
extra_env,
|
extra_env,
|
||||||
|
ansi_color_output,
|
||||||
} => {
|
} => {
|
||||||
let mut cmd = Command::new(toolchain::cargo());
|
let mut cmd = Command::new(toolchain::cargo());
|
||||||
cmd.arg(command);
|
cmd.arg(command);
|
||||||
cmd.current_dir(&self.root);
|
cmd.current_dir(&self.root);
|
||||||
cmd.args(["--workspace", "--message-format=json", "--manifest-path"])
|
cmd.arg("--workspace");
|
||||||
.arg(self.root.join("Cargo.toml").as_os_str());
|
|
||||||
|
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 {
|
for target in target_triples {
|
||||||
cmd.args(["--target", target.as_str()]);
|
cmd.args(["--target", target.as_str()]);
|
||||||
|
@ -160,7 +160,9 @@ config_data! {
|
|||||||
check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option<bool> = "null",
|
check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option<bool> = "null",
|
||||||
/// Override the command rust-analyzer uses instead of `cargo check` for
|
/// Override the command rust-analyzer uses instead of `cargo check` for
|
||||||
/// diagnostics on save. The command is required to output json and
|
/// 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
|
/// If you're changing this because you're using some tool wrapping
|
||||||
/// Cargo, you might also want to change
|
/// Cargo, you might also want to change
|
||||||
@ -1006,6 +1008,11 @@ impl Config {
|
|||||||
self.experimental("serverStatusNotification")
|
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 {
|
pub fn publish_diagnostics(&self) -> bool {
|
||||||
self.data.diagnostics_enable
|
self.data.diagnostics_enable
|
||||||
}
|
}
|
||||||
@ -1204,6 +1211,7 @@ impl Config {
|
|||||||
},
|
},
|
||||||
extra_args: self.data.check_extraArgs.clone(),
|
extra_args: self.data.check_extraArgs.clone(),
|
||||||
extra_env: self.check_on_save_extra_env(),
|
extra_env: self.check_on_save_extra_env(),
|
||||||
|
ansi_color_output: self.color_diagnostic_output(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -792,3 +792,29 @@ export interface ClientCommandOptions {
|
|||||||
commands: string[];
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -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
|
Override the command rust-analyzer uses instead of `cargo check` for
|
||||||
diagnostics on save. The command is required to output json and
|
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
|
If you're changing this because you're using some tool wrapping
|
||||||
Cargo, you might also want to change
|
Cargo, you might also want to change
|
||||||
|
11
editors/code/package-lock.json
generated
11
editors/code/package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.5.0-dev",
|
"version": "0.5.0-dev",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"anser": "^2.1.1",
|
||||||
"d3": "^7.6.1",
|
"d3": "^7.6.1",
|
||||||
"d3-graphviz": "^5.0.2",
|
"d3-graphviz": "^5.0.2",
|
||||||
"vscode-languageclient": "^8.0.2"
|
"vscode-languageclient": "^8.0.2"
|
||||||
@ -394,6 +395,11 @@
|
|||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/anser": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/anser/-/anser-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-nqLm4HxOTpeLOxcmB3QWmV5TcDFhW9y/fyQ+hivtDFcK4OQ+pQ5fzPnXHM1Mfcm0VkLtvVi1TCPr++Qy0Q/3EQ=="
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
@ -4096,6 +4102,11 @@
|
|||||||
"uri-js": "^4.2.2"
|
"uri-js": "^4.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"anser": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/anser/-/anser-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-nqLm4HxOTpeLOxcmB3QWmV5TcDFhW9y/fyQ+hivtDFcK4OQ+pQ5fzPnXHM1Mfcm0VkLtvVi1TCPr++Qy0Q/3EQ=="
|
||||||
|
},
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"test": "cross-env TEST_VARIABLE=test node ./out/tests/runTests.js"
|
"test": "cross-env TEST_VARIABLE=test node ./out/tests/runTests.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"anser": "^2.1.1",
|
||||||
"d3": "^7.6.1",
|
"d3": "^7.6.1",
|
||||||
"d3-graphviz": "^5.0.2",
|
"d3-graphviz": "^5.0.2",
|
||||||
"vscode-languageclient": "^8.0.2"
|
"vscode-languageclient": "^8.0.2"
|
||||||
@ -643,7 +644,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"rust-analyzer.check.overrideCommand": {
|
"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,
|
"default": null,
|
||||||
"type": [
|
"type": [
|
||||||
"null",
|
"null",
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import * as anser from "anser";
|
||||||
import * as lc from "vscode-languageclient/node";
|
import * as lc from "vscode-languageclient/node";
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import * as ra from "../src/lsp_ext";
|
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 * as diagnostics from "./diagnostics";
|
||||||
import { WorkspaceEdit } from "vscode";
|
import { WorkspaceEdit } from "vscode";
|
||||||
import { Config, substituteVSCodeVariables } from "./config";
|
import { Config, substituteVSCodeVariables } from "./config";
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
@ -120,12 +122,12 @@ export async function createClient(
|
|||||||
},
|
},
|
||||||
async handleDiagnostics(
|
async handleDiagnostics(
|
||||||
uri: vscode.Uri,
|
uri: vscode.Uri,
|
||||||
diagnostics: vscode.Diagnostic[],
|
diagnosticList: vscode.Diagnostic[],
|
||||||
next: lc.HandleDiagnosticsSignature
|
next: lc.HandleDiagnosticsSignature
|
||||||
) {
|
) {
|
||||||
const preview = config.previewRustcOutput;
|
const preview = config.previewRustcOutput;
|
||||||
const errorCode = config.useRustcErrorCode;
|
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
|
// 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
|
// Diagnostic class, if they ever break this we are out of luck and have to go
|
||||||
// back to the worst diagnostics experience ever:)
|
// back to the worst diagnostics experience ever:)
|
||||||
@ -138,9 +140,10 @@ export async function createClient(
|
|||||||
?.rendered;
|
?.rendered;
|
||||||
if (rendered) {
|
if (rendered) {
|
||||||
if (preview) {
|
if (preview) {
|
||||||
|
const decolorized = anser.ansiToText(rendered);
|
||||||
const index =
|
const index =
|
||||||
rendered.match(/^(note|help):/m)?.index || rendered.length;
|
decolorized.match(/^(note|help):/m)?.index || rendered.length;
|
||||||
diag.message = rendered
|
diag.message = decolorized
|
||||||
.substring(0, index)
|
.substring(0, index)
|
||||||
.replace(/^ -->[^\n]+\n/m, "");
|
.replace(/^ -->[^\n]+\n/m, "");
|
||||||
}
|
}
|
||||||
@ -154,8 +157,8 @@ export async function createClient(
|
|||||||
}
|
}
|
||||||
diag.code = {
|
diag.code = {
|
||||||
target: vscode.Uri.from({
|
target: vscode.Uri.from({
|
||||||
scheme: "rust-analyzer-diagnostics-view",
|
scheme: diagnostics.URI_SCHEME,
|
||||||
path: "/diagnostic message",
|
path: `/diagnostic message [${idx.toString()}]`,
|
||||||
fragment: uri.toString(),
|
fragment: uri.toString(),
|
||||||
query: idx.toString(),
|
query: idx.toString(),
|
||||||
}),
|
}),
|
||||||
@ -163,7 +166,7 @@ export async function createClient(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return next(uri, diagnostics);
|
return next(uri, diagnosticList);
|
||||||
},
|
},
|
||||||
async provideHover(
|
async provideHover(
|
||||||
document: vscode.TextDocument,
|
document: vscode.TextDocument,
|
||||||
@ -330,6 +333,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
|
|||||||
caps.codeActionGroup = true;
|
caps.codeActionGroup = true;
|
||||||
caps.hoverActions = true;
|
caps.hoverActions = true;
|
||||||
caps.serverStatusNotification = true;
|
caps.serverStatusNotification = true;
|
||||||
|
caps.colorDiagnosticOutput = true;
|
||||||
caps.commands = {
|
caps.commands = {
|
||||||
commands: [
|
commands: [
|
||||||
"rust-analyzer.runSingle",
|
"rust-analyzer.runSingle",
|
||||||
|
212
editors/code/src/diagnostics.ts
Normal file
212
editors/code/src/diagnostics.ts
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import * as anser from "anser";
|
||||||
|
import * as vscode from "vscode";
|
||||||
|
import { ProviderResult, Range, TextEditorDecorationType, ThemeColor, window } from "vscode";
|
||||||
|
import { Ctx } from "./ctx";
|
||||||
|
|
||||||
|
export const URI_SCHEME = "rust-analyzer-diagnostics-view";
|
||||||
|
|
||||||
|
export class TextDocumentProvider implements vscode.TextDocumentContentProvider {
|
||||||
|
private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
|
||||||
|
|
||||||
|
public constructor(private readonly ctx: Ctx) {}
|
||||||
|
|
||||||
|
get onDidChange(): vscode.Event<vscode.Uri> {
|
||||||
|
return this._onDidChange.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerUpdate(uri: vscode.Uri) {
|
||||||
|
if (uri.scheme === URI_SCHEME) {
|
||||||
|
this._onDidChange.fire(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._onDidChange.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async provideTextDocumentContent(uri: vscode.Uri): Promise<string> {
|
||||||
|
const contents = getRenderedDiagnostic(this.ctx, uri);
|
||||||
|
return anser.ansiToText(contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRenderedDiagnostic(ctx: Ctx, uri: vscode.Uri): 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;
|
||||||
|
|
||||||
|
if (!rendered) {
|
||||||
|
return "Unable to find original rustc diagnostic";
|
||||||
|
}
|
||||||
|
|
||||||
|
return rendered;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnserStyle {
|
||||||
|
fg: string;
|
||||||
|
bg: string;
|
||||||
|
fg_truecolor: string;
|
||||||
|
bg_truecolor: string;
|
||||||
|
decorations: Array<anser.DecorationName>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AnsiDecorationProvider implements vscode.Disposable {
|
||||||
|
private _decorationTypes = new Map<AnserStyle, TextEditorDecorationType>();
|
||||||
|
|
||||||
|
public constructor(private readonly ctx: Ctx) {}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
for (const decorationType of this._decorationTypes.values()) {
|
||||||
|
decorationType.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._decorationTypes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
async provideDecorations(editor: vscode.TextEditor) {
|
||||||
|
if (editor.document.uri.scheme !== URI_SCHEME) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decorations = (await this._getDecorations(editor.document.uri)) || [];
|
||||||
|
for (const [decorationType, ranges] of decorations) {
|
||||||
|
editor.setDecorations(decorationType, ranges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDecorations(
|
||||||
|
uri: vscode.Uri
|
||||||
|
): ProviderResult<[TextEditorDecorationType, Range[]][]> {
|
||||||
|
const stringContents = getRenderedDiagnostic(this.ctx, uri);
|
||||||
|
const lines = stringContents.split("\n");
|
||||||
|
|
||||||
|
const result = new Map<TextEditorDecorationType, Range[]>();
|
||||||
|
// Populate all known decoration types in the result. This forces any
|
||||||
|
// lingering decorations to be cleared if the text content changes to
|
||||||
|
// something without ANSI codes for a given decoration type.
|
||||||
|
for (const decorationType of this._decorationTypes.values()) {
|
||||||
|
result.set(decorationType, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [lineNumber, line] of lines.entries()) {
|
||||||
|
const totalEscapeLength = 0;
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
const parsed = anser.ansiToJson(line, { use_classes: true });
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (const span of parsed) {
|
||||||
|
const { content, ...style } = span;
|
||||||
|
|
||||||
|
const range = new Range(
|
||||||
|
lineNumber,
|
||||||
|
offset - totalEscapeLength,
|
||||||
|
lineNumber,
|
||||||
|
offset + content.length - totalEscapeLength
|
||||||
|
);
|
||||||
|
|
||||||
|
offset += content.length;
|
||||||
|
|
||||||
|
const decorationType = this._getDecorationType(style);
|
||||||
|
|
||||||
|
if (!result.has(decorationType)) {
|
||||||
|
result.set(decorationType, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.get(decorationType)!.push(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...result];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDecorationType(style: AnserStyle): TextEditorDecorationType {
|
||||||
|
let decorationType = this._decorationTypes.get(style);
|
||||||
|
|
||||||
|
if (decorationType) {
|
||||||
|
return decorationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fontWeight = style.decorations.find((s) => s === "bold");
|
||||||
|
const fontStyle = style.decorations.find((s) => s === "italic");
|
||||||
|
const textDecoration = style.decorations.find((s) => s === "underline");
|
||||||
|
|
||||||
|
decorationType = window.createTextEditorDecorationType({
|
||||||
|
backgroundColor: AnsiDecorationProvider._convertColor(style.bg, style.bg_truecolor),
|
||||||
|
color: AnsiDecorationProvider._convertColor(style.fg, style.fg_truecolor),
|
||||||
|
fontWeight,
|
||||||
|
fontStyle,
|
||||||
|
textDecoration,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._decorationTypes.set(style, decorationType);
|
||||||
|
|
||||||
|
return decorationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This could just be a kebab-case to camelCase conversion, but I think it's
|
||||||
|
// a short enough list to just write these by hand
|
||||||
|
static readonly _anserToThemeColor: Record<string, ThemeColor> = {
|
||||||
|
"ansi-black": "ansiBlack",
|
||||||
|
"ansi-white": "ansiWhite",
|
||||||
|
"ansi-red": "ansiRed",
|
||||||
|
"ansi-green": "ansiGreen",
|
||||||
|
"ansi-yellow": "ansiYellow",
|
||||||
|
"ansi-blue": "ansiBlue",
|
||||||
|
"ansi-magenta": "ansiMagenta",
|
||||||
|
"ansi-cyan": "ansiCyan",
|
||||||
|
|
||||||
|
"ansi-bright-black": "ansiBrightBlack",
|
||||||
|
"ansi-bright-white": "ansiBrightWhite",
|
||||||
|
"ansi-bright-red": "ansiBrightRed",
|
||||||
|
"ansi-bright-green": "ansiBrightGreen",
|
||||||
|
"ansi-bright-yellow": "ansiBrightYellow",
|
||||||
|
"ansi-bright-blue": "ansiBrightBlue",
|
||||||
|
"ansi-bright-magenta": "ansiBrightMagenta",
|
||||||
|
"ansi-bright-cyan": "ansiBrightCyan",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static _convertColor(
|
||||||
|
color?: string,
|
||||||
|
truecolor?: string
|
||||||
|
): ThemeColor | string | undefined {
|
||||||
|
if (!color) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color === "ansi-truecolor") {
|
||||||
|
if (!truecolor) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return `rgb(${truecolor})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paletteMatch = color.match(/ansi-palette-(.+)/);
|
||||||
|
if (paletteMatch) {
|
||||||
|
const paletteColor = paletteMatch[1];
|
||||||
|
// anser won't return both the RGB and the color name at the same time,
|
||||||
|
// so just fake a single foreground control char with the palette number:
|
||||||
|
const spans = anser.ansiToJson(`\x1b[38;5;${paletteColor}m`);
|
||||||
|
const rgb = spans[1].fg;
|
||||||
|
|
||||||
|
if (rgb) {
|
||||||
|
return `rgb(${rgb})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeColor = AnsiDecorationProvider._anserToThemeColor[color];
|
||||||
|
if (themeColor) {
|
||||||
|
return new ThemeColor("terminal." + themeColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import * as lc from "vscode-languageclient/node";
|
|||||||
|
|
||||||
import * as commands from "./commands";
|
import * as commands from "./commands";
|
||||||
import { CommandFactory, Ctx, fetchWorkspace } from "./ctx";
|
import { CommandFactory, Ctx, fetchWorkspace } from "./ctx";
|
||||||
|
import * as diagnostics from "./diagnostics";
|
||||||
import { activateTaskProvider } from "./tasks";
|
import { activateTaskProvider } from "./tasks";
|
||||||
import { setContextValue } from "./util";
|
import { setContextValue } from "./util";
|
||||||
|
|
||||||
@ -48,30 +49,52 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
|
|||||||
ctx.pushExtCleanup(activateTaskProvider(ctx.config));
|
ctx.pushExtCleanup(activateTaskProvider(ctx.config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const diagnosticProvider = new diagnostics.TextDocumentProvider(ctx);
|
||||||
ctx.pushExtCleanup(
|
ctx.pushExtCleanup(
|
||||||
vscode.workspace.registerTextDocumentContentProvider(
|
vscode.workspace.registerTextDocumentContentProvider(
|
||||||
"rust-analyzer-diagnostics-view",
|
diagnostics.URI_SCHEME,
|
||||||
new (class implements vscode.TextDocumentContentProvider {
|
diagnosticProvider
|
||||||
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";
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const decorationProvider = new diagnostics.AnsiDecorationProvider(ctx);
|
||||||
|
ctx.pushExtCleanup(decorationProvider);
|
||||||
|
|
||||||
|
async function decorateVisibleEditors(document: vscode.TextDocument) {
|
||||||
|
for (const editor of vscode.window.visibleTextEditors) {
|
||||||
|
if (document === editor.document) {
|
||||||
|
await decorationProvider.provideDecorations(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vscode.workspace.onDidChangeTextDocument(
|
||||||
|
async (event) => await decorateVisibleEditors(event.document),
|
||||||
|
null,
|
||||||
|
ctx.subscriptions
|
||||||
|
);
|
||||||
|
vscode.workspace.onDidOpenTextDocument(decorateVisibleEditors, null, ctx.subscriptions);
|
||||||
|
vscode.window.onDidChangeActiveTextEditor(
|
||||||
|
async (editor) => {
|
||||||
|
if (editor) {
|
||||||
|
diagnosticProvider.triggerUpdate(editor.document.uri);
|
||||||
|
await decorateVisibleEditors(editor.document);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
ctx.subscriptions
|
||||||
|
);
|
||||||
|
vscode.window.onDidChangeVisibleTextEditors(
|
||||||
|
async (visibleEditors) => {
|
||||||
|
for (const editor of visibleEditors) {
|
||||||
|
diagnosticProvider.triggerUpdate(editor.document.uri);
|
||||||
|
await decorationProvider.provideDecorations(editor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
ctx.subscriptions
|
||||||
|
);
|
||||||
|
|
||||||
vscode.workspace.onDidChangeWorkspaceFolders(
|
vscode.workspace.onDidChangeWorkspaceFolders(
|
||||||
async (_) => ctx.onWorkspaceFolderChanges(),
|
async (_) => ctx.onWorkspaceFolderChanges(),
|
||||||
null,
|
null,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user