diff options
Diffstat (limited to 'editors/code/src/ctx.ts')
-rw-r--r-- | editors/code/src/ctx.ts | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts new file mode 100644 index 000000000..a2a4e42a9 --- /dev/null +++ b/editors/code/src/ctx.ts | |||
@@ -0,0 +1,112 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import * as lc from 'vscode-languageclient'; | ||
3 | import { Config } from './config'; | ||
4 | import { createClient } from './client'; | ||
5 | |||
6 | export class Ctx { | ||
7 | readonly config: Config; | ||
8 | // Because we have "reload server" action, various listeners **will** face a | ||
9 | // situation where the client is not ready yet, and should be prepared to | ||
10 | // deal with it. | ||
11 | // | ||
12 | // Ideally, this should be replaced with async getter though. | ||
13 | client: lc.LanguageClient | null = null; | ||
14 | private extCtx: vscode.ExtensionContext; | ||
15 | private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = []; | ||
16 | |||
17 | constructor(extCtx: vscode.ExtensionContext) { | ||
18 | this.config = new Config(extCtx); | ||
19 | this.extCtx = extCtx; | ||
20 | } | ||
21 | |||
22 | async restartServer() { | ||
23 | let old = this.client; | ||
24 | if (old) { | ||
25 | await old.stop(); | ||
26 | } | ||
27 | this.client = null; | ||
28 | const client = createClient(this.config); | ||
29 | this.pushCleanup(client.start()); | ||
30 | await client.onReady(); | ||
31 | |||
32 | this.client = client; | ||
33 | for (const hook of this.onDidRestartHooks) { | ||
34 | hook(client); | ||
35 | } | ||
36 | } | ||
37 | |||
38 | get activeRustEditor(): vscode.TextEditor | undefined { | ||
39 | const editor = vscode.window.activeTextEditor; | ||
40 | return editor && editor.document.languageId === 'rust' | ||
41 | ? editor | ||
42 | : undefined; | ||
43 | } | ||
44 | |||
45 | registerCommand(name: string, factory: (ctx: Ctx) => Cmd) { | ||
46 | const fullName = `rust-analyzer.${name}`; | ||
47 | const cmd = factory(this); | ||
48 | const d = vscode.commands.registerCommand(fullName, cmd); | ||
49 | this.pushCleanup(d); | ||
50 | } | ||
51 | |||
52 | overrideCommand(name: string, factory: (ctx: Ctx) => Cmd) { | ||
53 | const defaultCmd = `default:${name}`; | ||
54 | const override = factory(this); | ||
55 | const original = (...args: any[]) => | ||
56 | vscode.commands.executeCommand(defaultCmd, ...args); | ||
57 | try { | ||
58 | const d = vscode.commands.registerCommand( | ||
59 | name, | ||
60 | async (...args: any[]) => { | ||
61 | if (!(await override(...args))) { | ||
62 | return await original(...args); | ||
63 | } | ||
64 | }, | ||
65 | ); | ||
66 | this.pushCleanup(d); | ||
67 | } catch (_) { | ||
68 | vscode.window.showWarningMessage( | ||
69 | 'Enhanced typing feature is disabled because of incompatibility with VIM extension, consider turning off rust-analyzer.enableEnhancedTyping: https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/README.md#settings', | ||
70 | ); | ||
71 | } | ||
72 | } | ||
73 | |||
74 | get subscriptions(): { dispose(): any }[] { | ||
75 | return this.extCtx.subscriptions; | ||
76 | } | ||
77 | |||
78 | pushCleanup(d: { dispose(): any }) { | ||
79 | this.extCtx.subscriptions.push(d); | ||
80 | } | ||
81 | |||
82 | onDidRestart(hook: (client: lc.LanguageClient) => void) { | ||
83 | this.onDidRestartHooks.push(hook); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | export type Cmd = (...args: any[]) => any; | ||
88 | |||
89 | export async function sendRequestWithRetry<R>( | ||
90 | client: lc.LanguageClient, | ||
91 | method: string, | ||
92 | param: any, | ||
93 | token?: vscode.CancellationToken, | ||
94 | ): Promise<R> { | ||
95 | for (const delay of [2, 4, 6, 8, 10, null]) { | ||
96 | try { | ||
97 | return await (token ? client.sendRequest(method, param, token) : client.sendRequest(method, param)); | ||
98 | } catch (e) { | ||
99 | if ( | ||
100 | e.code === lc.ErrorCodes.ContentModified && | ||
101 | delay !== null | ||
102 | ) { | ||
103 | await sleep(10 * (1 << delay)); | ||
104 | continue; | ||
105 | } | ||
106 | throw e; | ||
107 | } | ||
108 | } | ||
109 | throw 'unreachable'; | ||
110 | } | ||
111 | |||
112 | const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); | ||