]> git.lizzy.rs Git - rust.git/blob - editors/code/src/util.ts
Drop extensionUri copy
[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 isCargoTomlDocument(document: vscode.TextDocument): document is RustDocument {
108     // ideally `document.languageId` should be 'toml' but user maybe not have toml extension installed
109     return document.uri.scheme === 'file' && document.fileName.endsWith('Cargo.toml');
110 }
111
112 export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
113     return isRustDocument(editor.document);
114 }
115
116 export function isValidExecutable(path: string): boolean {
117     log.debug("Checking availability of a binary at", path);
118
119     const res = spawnSync(path, ["--version"], { encoding: 'utf8' });
120
121     const printOutput = res.error && (res.error as any).code !== 'ENOENT' ? log.warn : log.debug;
122     printOutput(path, "--version:", res);
123
124     return res.status === 0;
125 }
126
127 /** Sets ['when'](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts) clause contexts */
128 export function setContextValue(key: string, value: any): Thenable<void> {
129     return vscode.commands.executeCommand('setContext', key, value);
130 }
131
132 /**
133  * Returns a higher-order function that caches the results of invoking the
134  * underlying async function.
135  */
136 export function memoizeAsync<Ret, TThis, Param extends string>(func: (this: TThis, arg: Param) => Promise<Ret>) {
137     const cache = new Map<string, Ret>();
138
139     return async function(this: TThis, arg: Param) {
140         const cached = cache.get(arg);
141         if (cached) return cached;
142
143         const result = await func.call(this, arg);
144         cache.set(arg, result);
145
146         return result;
147     };
148 }
149
150 /** Awaitable wrapper around `child_process.exec` */
151 export function execute(command: string, options: ExecOptions): Promise<string> {
152     return new Promise((resolve, reject) => {
153         exec(command, options, (err, stdout, stderr) => {
154             if (err) {
155                 reject(err);
156                 return;
157             }
158
159             if (stderr) {
160                 reject(new Error(stderr));
161                 return;
162             }
163
164             resolve(stdout.trimEnd());
165         });
166     });
167 }