]> git.lizzy.rs Git - rust.git/blob - editors/code/src/util.ts
Auto merge of #13647 - nyz93:fix/tuple-to-named-struct, r=Veykril
[rust.git] / editors / code / src / util.ts
1 import * as lc from "vscode-languageclient/node";
2 import * as vscode from "vscode";
3 import { strict as nativeAssert } from "assert";
4 import { exec, ExecOptions, spawnSync } from "child_process";
5 import { inspect } from "util";
6
7 export function assert(condition: boolean, explanation: string): asserts condition {
8     try {
9         nativeAssert(condition, explanation);
10     } catch (err) {
11         log.error(`Assertion failed:`, explanation);
12         throw err;
13     }
14 }
15
16 export const log = new (class {
17     private enabled = true;
18     private readonly output = vscode.window.createOutputChannel("Rust Analyzer Client");
19
20     setEnabled(yes: boolean): void {
21         log.enabled = yes;
22     }
23
24     // Hint: the type [T, ...T[]] means a non-empty array
25     debug(...msg: [unknown, ...unknown[]]): void {
26         if (!log.enabled) return;
27         log.write("DEBUG", ...msg);
28     }
29
30     info(...msg: [unknown, ...unknown[]]): void {
31         log.write("INFO", ...msg);
32     }
33
34     warn(...msg: [unknown, ...unknown[]]): void {
35         debugger;
36         log.write("WARN", ...msg);
37     }
38
39     error(...msg: [unknown, ...unknown[]]): void {
40         debugger;
41         log.write("ERROR", ...msg);
42         log.output.show(true);
43     }
44
45     private write(label: string, ...messageParts: unknown[]): void {
46         const message = messageParts.map(log.stringify).join(" ");
47         const dateTime = new Date().toLocaleString();
48         log.output.appendLine(`${label} [${dateTime}]: ${message}`);
49     }
50
51     private stringify(val: unknown): string {
52         if (typeof val === "string") return val;
53         return inspect(val, {
54             colors: false,
55             depth: 6, // heuristic
56         });
57     }
58 })();
59
60 export async function sendRequestWithRetry<TParam, TRet>(
61     client: lc.LanguageClient,
62     reqType: lc.RequestType<TParam, TRet, unknown>,
63     param: TParam,
64     token?: vscode.CancellationToken
65 ): Promise<TRet> {
66     // The sequence is `10 * (2 ** (2 * n))` where n is 1, 2, 3...
67     for (const delay of [40, 160, 640, 2560, 10240, null]) {
68         try {
69             return await (token
70                 ? client.sendRequest(reqType, param, token)
71                 : client.sendRequest(reqType, param));
72         } catch (error) {
73             if (delay === null) {
74                 log.warn("LSP request timed out", { method: reqType.method, param, error });
75                 throw error;
76             }
77             if (error.code === lc.LSPErrorCodes.RequestCancelled) {
78                 throw error;
79             }
80
81             if (error.code !== lc.LSPErrorCodes.ContentModified) {
82                 log.warn("LSP request failed", { method: reqType.method, param, error });
83                 throw error;
84             }
85             await sleep(delay);
86         }
87     }
88     throw "unreachable";
89 }
90
91 export function sleep(ms: number) {
92     return new Promise((resolve) => setTimeout(resolve, ms));
93 }
94
95 export type RustDocument = vscode.TextDocument & { languageId: "rust" };
96 export type RustEditor = vscode.TextEditor & { document: RustDocument };
97
98 export function isRustDocument(document: vscode.TextDocument): document is RustDocument {
99     // Prevent corrupted text (particularly via inlay hints) in diff views
100     // by allowing only `file` schemes
101     // unfortunately extensions that use diff views not always set this
102     // to something different than 'file' (see ongoing bug: #4608)
103     return document.languageId === "rust" && document.uri.scheme === "file";
104 }
105
106 export function isCargoTomlDocument(document: vscode.TextDocument): document is RustDocument {
107     // ideally `document.languageId` should be 'toml' but user maybe not have toml extension installed
108     return document.uri.scheme === "file" && document.fileName.endsWith("Cargo.toml");
109 }
110
111 export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
112     return isRustDocument(editor.document);
113 }
114
115 export function isValidExecutable(path: string): boolean {
116     log.debug("Checking availability of a binary at", path);
117
118     const res = spawnSync(path, ["--version"], { encoding: "utf8" });
119
120     const printOutput = res.error && (res.error as any).code !== "ENOENT" ? log.warn : log.debug;
121     printOutput(path, "--version:", res);
122
123     return res.status === 0;
124 }
125
126 /** Sets ['when'](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts) clause contexts */
127 export function setContextValue(key: string, value: any): Thenable<void> {
128     return vscode.commands.executeCommand("setContext", key, value);
129 }
130
131 /**
132  * Returns a higher-order function that caches the results of invoking the
133  * underlying async function.
134  */
135 export function memoizeAsync<Ret, TThis, Param extends string>(
136     func: (this: TThis, arg: Param) => Promise<Ret>
137 ) {
138     const cache = new Map<string, Ret>();
139
140     return async function (this: TThis, arg: Param) {
141         const cached = cache.get(arg);
142         if (cached) return cached;
143
144         const result = await func.call(this, arg);
145         cache.set(arg, result);
146
147         return result;
148     };
149 }
150
151 /** Awaitable wrapper around `child_process.exec` */
152 export function execute(command: string, options: ExecOptions): Promise<string> {
153     return new Promise((resolve, reject) => {
154         exec(command, options, (err, stdout, stderr) => {
155             if (err) {
156                 reject(err);
157                 return;
158             }
159
160             if (stderr) {
161                 reject(new Error(stderr));
162                 return;
163             }
164
165             resolve(stdout.trimEnd());
166         });
167     });
168 }