]> git.lizzy.rs Git - rust.git/blob - editors/code/src/util.ts
Add special `auto` value for `debug.sourceFileMap`
[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             );
73         } catch (error) {
74             if (delay === null) {
75                 log.warn("LSP request timed out", { method: reqType.method, param, error });
76                 throw error;
77             }
78             if (error.code === lc.LSPErrorCodes.RequestCancelled) {
79                 throw error;
80             }
81
82             if (error.code !== lc.LSPErrorCodes.ContentModified) {
83                 log.warn("LSP request failed", { method: reqType.method, param, error });
84                 throw error;
85             }
86             await sleep(delay);
87         }
88     }
89     throw 'unreachable';
90 }
91
92 export function sleep(ms: number) {
93     return new Promise(resolve => setTimeout(resolve, ms));
94 }
95
96 export type RustDocument = vscode.TextDocument & { languageId: "rust" };
97 export type RustEditor = vscode.TextEditor & { document: RustDocument };
98
99 export function isRustDocument(document: vscode.TextDocument): document is RustDocument {
100     // Prevent corrupted text (particularly via inlay hints) in diff views
101     // by allowing only `file` schemes
102     // unfortunately extensions that use diff views not always set this
103     // to something different than 'file' (see ongoing bug: #4608)
104     return document.languageId === 'rust' && document.uri.scheme === 'file';
105 }
106
107 export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
108     return isRustDocument(editor.document);
109 }
110
111 export function isValidExecutable(path: string): boolean {
112     log.debug("Checking availability of a binary at", path);
113
114     const res = spawnSync(path, ["--version"], { encoding: 'utf8' });
115
116     const printOutput = res.error && (res.error as any).code !== 'ENOENT' ? log.warn : log.debug;
117     printOutput(path, "--version:", res);
118
119     return res.status === 0;
120 }
121
122 /** Sets ['when'](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts) clause contexts */
123 export function setContextValue(key: string, value: any): Thenable<void> {
124     return vscode.commands.executeCommand('setContext', key, value);
125 }
126
127 /**
128  * Returns a higher-order function that caches the results of invoking the
129  * underlying function.
130  */
131 export function memoize<Ret, TThis, Param extends string>(func: (this: TThis, arg: Param) => Ret) {
132     const cache = new Map<string, Ret>();
133
134     return function(this: TThis, arg: Param) {
135         const cached = cache.get(arg);
136         if (cached) return cached;
137
138         const result = func.call(this, arg);
139         cache.set(arg, result);
140
141         return result;
142     };
143 }
144
145 /** Awaitable wrapper around `child_process.exec` */
146 export function execute(command: string, options: ExecOptions): Promise<string> {
147     return new Promise((resolve, reject) => {
148         exec(command, options, (err, stdout, stderr) => {
149             if (err) {
150                 reject(err);
151                 return;
152             }
153
154             if (stderr) {
155                 reject(new Error(stderr));
156                 return;
157             }
158
159             resolve(stdout.trimEnd());
160         });
161     });
162 }