Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'editors/code/src/diagnostics.ts')
-rw-r--r--editors/code/src/diagnostics.ts212
1 files changed, 212 insertions, 0 deletions
diff --git a/editors/code/src/diagnostics.ts b/editors/code/src/diagnostics.ts
new file mode 100644
index 0000000000..9695d8bf26
--- /dev/null
+++ b/editors/code/src/diagnostics.ts
@@ -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;
+ }
+}