diff options
author | Aleksey Kladov <[email protected]> | 2019-12-31 17:14:00 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-12-31 17:32:17 +0000 |
commit | 087af54069d34eef5197e04d64ac322d9ee98085 (patch) | |
tree | 3a6e4b1884930c07bd800a771ffd777d7a866b11 /editors | |
parent | 0849f7001cac6af93ce9e9356f8c21881bbe34c5 (diff) |
Refactor server lifecycle
Diffstat (limited to 'editors')
-rw-r--r-- | editors/code/src/client.ts | 90 | ||||
-rw-r--r-- | editors/code/src/commands/analyzer_status.ts | 5 | ||||
-rw-r--r-- | editors/code/src/commands/expand_macro.ts | 5 | ||||
-rw-r--r-- | editors/code/src/commands/index.ts | 25 | ||||
-rw-r--r-- | editors/code/src/commands/join_lines.ts | 7 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 76 | ||||
-rw-r--r-- | editors/code/src/highlighting.ts | 56 | ||||
-rw-r--r-- | editors/code/src/inlay_hints.ts | 11 | ||||
-rw-r--r-- | editors/code/src/main.ts | 35 | ||||
-rw-r--r-- | editors/code/src/server.ts | 96 | ||||
-rw-r--r-- | editors/code/src/source_change.ts | 9 | ||||
-rw-r--r-- | editors/code/src/status_display.ts | 4 |
12 files changed, 218 insertions, 201 deletions
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts new file mode 100644 index 000000000..94948b10f --- /dev/null +++ b/editors/code/src/client.ts | |||
@@ -0,0 +1,90 @@ | |||
1 | import { homedir } from 'os'; | ||
2 | import * as lc from 'vscode-languageclient'; | ||
3 | |||
4 | import { window, workspace } from 'vscode'; | ||
5 | import { Config } from './config'; | ||
6 | |||
7 | export function createClient(config: Config): lc.LanguageClient { | ||
8 | // '.' Is the fallback if no folder is open | ||
9 | // TODO?: Workspace folders support Uri's (eg: file://test.txt). It might be a good idea to test if the uri points to a file. | ||
10 | let folder: string = '.'; | ||
11 | if (workspace.workspaceFolders !== undefined) { | ||
12 | folder = workspace.workspaceFolders[0].uri.fsPath.toString(); | ||
13 | } | ||
14 | |||
15 | const command = expandPathResolving(config.raLspServerPath); | ||
16 | const run: lc.Executable = { | ||
17 | command, | ||
18 | options: { cwd: folder }, | ||
19 | }; | ||
20 | const serverOptions: lc.ServerOptions = { | ||
21 | run, | ||
22 | debug: run, | ||
23 | }; | ||
24 | const traceOutputChannel = window.createOutputChannel( | ||
25 | 'Rust Analyzer Language Server Trace', | ||
26 | ); | ||
27 | const clientOptions: lc.LanguageClientOptions = { | ||
28 | documentSelector: [{ scheme: 'file', language: 'rust' }], | ||
29 | initializationOptions: { | ||
30 | publishDecorations: true, | ||
31 | lruCapacity: config.lruCapacity, | ||
32 | maxInlayHintLength: config.maxInlayHintLength, | ||
33 | cargoWatchEnable: config.cargoWatchOptions.enable, | ||
34 | cargoWatchArgs: config.cargoWatchOptions.arguments, | ||
35 | cargoWatchCommand: config.cargoWatchOptions.command, | ||
36 | cargoWatchAllTargets: | ||
37 | config.cargoWatchOptions.allTargets, | ||
38 | excludeGlobs: config.excludeGlobs, | ||
39 | useClientWatching: config.useClientWatching, | ||
40 | featureFlags: config.featureFlags, | ||
41 | withSysroot: config.withSysroot, | ||
42 | cargoFeatures: config.cargoFeatures, | ||
43 | }, | ||
44 | traceOutputChannel, | ||
45 | }; | ||
46 | |||
47 | const res = new lc.LanguageClient( | ||
48 | 'rust-analyzer', | ||
49 | 'Rust Analyzer Language Server', | ||
50 | serverOptions, | ||
51 | clientOptions, | ||
52 | ); | ||
53 | |||
54 | // HACK: This is an awful way of filtering out the decorations notifications | ||
55 | // However, pending proper support, this is the most effecitve approach | ||
56 | // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages | ||
57 | // Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting) | ||
58 | // This also requires considering our settings strategy, which is work which needs doing | ||
59 | // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests | ||
60 | res._tracer = { | ||
61 | log: (messageOrDataObject: string | any, data?: string) => { | ||
62 | if (typeof messageOrDataObject === 'string') { | ||
63 | if ( | ||
64 | messageOrDataObject.includes( | ||
65 | 'rust-analyzer/publishDecorations', | ||
66 | ) || | ||
67 | messageOrDataObject.includes( | ||
68 | 'rust-analyzer/decorationsRequest', | ||
69 | ) | ||
70 | ) { | ||
71 | // Don't log publish decorations requests | ||
72 | } else { | ||
73 | // @ts-ignore This is just a utility function | ||
74 | res.logTrace(messageOrDataObject, data); | ||
75 | } | ||
76 | } else { | ||
77 | // @ts-ignore | ||
78 | res.logObjectTrace(messageOrDataObject); | ||
79 | } | ||
80 | }, | ||
81 | }; | ||
82 | res.registerProposedFeatures() | ||
83 | return res; | ||
84 | } | ||
85 | function expandPathResolving(path: string) { | ||
86 | if (path.startsWith('~/')) { | ||
87 | return path.replace('~', homedir()); | ||
88 | } | ||
89 | return path; | ||
90 | } | ||
diff --git a/editors/code/src/commands/analyzer_status.ts b/editors/code/src/commands/analyzer_status.ts index 2c8362286..cf37dc6f0 100644 --- a/editors/code/src/commands/analyzer_status.ts +++ b/editors/code/src/commands/analyzer_status.ts | |||
@@ -49,9 +49,10 @@ class TextDocumentContentProvider | |||
49 | _uri: vscode.Uri, | 49 | _uri: vscode.Uri, |
50 | ): vscode.ProviderResult<string> { | 50 | ): vscode.ProviderResult<string> { |
51 | const editor = vscode.window.activeTextEditor; | 51 | const editor = vscode.window.activeTextEditor; |
52 | if (editor == null) return ''; | 52 | const client = this.ctx.client |
53 | if (!editor || !client) return ''; | ||
53 | 54 | ||
54 | return this.ctx.client.sendRequest<string>( | 55 | return client.sendRequest<string>( |
55 | 'rust-analyzer/analyzerStatus', | 56 | 'rust-analyzer/analyzerStatus', |
56 | null, | 57 | null, |
57 | ); | 58 | ); |
diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts index da208257a..472f43b8d 100644 --- a/editors/code/src/commands/expand_macro.ts +++ b/editors/code/src/commands/expand_macro.ts | |||
@@ -52,14 +52,15 @@ class TextDocumentContentProvider | |||
52 | 52 | ||
53 | async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> { | 53 | async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> { |
54 | const editor = vscode.window.activeTextEditor; | 54 | const editor = vscode.window.activeTextEditor; |
55 | if (editor == null) return ''; | 55 | const client = this.ctx.client |
56 | if (!editor || !client) return ''; | ||
56 | 57 | ||
57 | const position = editor.selection.active; | 58 | const position = editor.selection.active; |
58 | const request: lc.TextDocumentPositionParams = { | 59 | const request: lc.TextDocumentPositionParams = { |
59 | textDocument: { uri: editor.document.uri.toString() }, | 60 | textDocument: { uri: editor.document.uri.toString() }, |
60 | position, | 61 | position, |
61 | }; | 62 | }; |
62 | const expanded = await this.ctx.client.sendRequest<ExpandedMacro>( | 63 | const expanded = await client.sendRequest<ExpandedMacro>( |
63 | 'rust-analyzer/expandMacro', | 64 | 'rust-analyzer/expandMacro', |
64 | request, | 65 | request, |
65 | ); | 66 | ); |
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index c28709c8a..4431fdcf6 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts | |||
@@ -15,18 +15,21 @@ import { run, runSingle } from './runnables'; | |||
15 | 15 | ||
16 | function collectGarbage(ctx: Ctx): Cmd { | 16 | function collectGarbage(ctx: Ctx): Cmd { |
17 | return async () => { | 17 | return async () => { |
18 | ctx.client.sendRequest<null>('rust-analyzer/collectGarbage', null); | 18 | ctx.client?.sendRequest<null>('rust-analyzer/collectGarbage', null); |
19 | }; | 19 | }; |
20 | } | 20 | } |
21 | 21 | ||
22 | function showReferences(ctx: Ctx): Cmd { | 22 | function showReferences(ctx: Ctx): Cmd { |
23 | return (uri: string, position: lc.Position, locations: lc.Location[]) => { | 23 | return (uri: string, position: lc.Position, locations: lc.Location[]) => { |
24 | vscode.commands.executeCommand( | 24 | let client = ctx.client; |
25 | 'editor.action.showReferences', | 25 | if (client) { |
26 | vscode.Uri.parse(uri), | 26 | vscode.commands.executeCommand( |
27 | ctx.client.protocol2CodeConverter.asPosition(position), | 27 | 'editor.action.showReferences', |
28 | locations.map(ctx.client.protocol2CodeConverter.asLocation), | 28 | vscode.Uri.parse(uri), |
29 | ); | 29 | client.protocol2CodeConverter.asPosition(position), |
30 | locations.map(client.protocol2CodeConverter.asLocation), | ||
31 | ); | ||
32 | } | ||
30 | }; | 33 | }; |
31 | } | 34 | } |
32 | 35 | ||
@@ -36,6 +39,13 @@ function applySourceChange(ctx: Ctx): Cmd { | |||
36 | } | 39 | } |
37 | } | 40 | } |
38 | 41 | ||
42 | function reload(ctx: Ctx): Cmd { | ||
43 | return async () => { | ||
44 | vscode.window.showInformationMessage('Reloading rust-analyzer...'); | ||
45 | await ctx.restartServer(); | ||
46 | } | ||
47 | } | ||
48 | |||
39 | export { | 49 | export { |
40 | analyzerStatus, | 50 | analyzerStatus, |
41 | expandMacro, | 51 | expandMacro, |
@@ -49,4 +59,5 @@ export { | |||
49 | runSingle, | 59 | runSingle, |
50 | showReferences, | 60 | showReferences, |
51 | applySourceChange, | 61 | applySourceChange, |
62 | reload | ||
52 | }; | 63 | }; |
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts index f4f902cf9..7b08c3255 100644 --- a/editors/code/src/commands/join_lines.ts +++ b/editors/code/src/commands/join_lines.ts | |||
@@ -6,13 +6,14 @@ import { applySourceChange, SourceChange } from '../source_change'; | |||
6 | export function joinLines(ctx: Ctx): Cmd { | 6 | export function joinLines(ctx: Ctx): Cmd { |
7 | return async () => { | 7 | return async () => { |
8 | const editor = ctx.activeRustEditor; | 8 | const editor = ctx.activeRustEditor; |
9 | if (!editor) return; | 9 | const client = ctx.client; |
10 | if (!editor || !client) return; | ||
10 | 11 | ||
11 | const request: JoinLinesParams = { | 12 | const request: JoinLinesParams = { |
12 | range: ctx.client.code2ProtocolConverter.asRange(editor.selection), | 13 | range: client.code2ProtocolConverter.asRange(editor.selection), |
13 | textDocument: { uri: editor.document.uri.toString() }, | 14 | textDocument: { uri: editor.document.uri.toString() }, |
14 | }; | 15 | }; |
15 | const change = await ctx.client.sendRequest<SourceChange>( | 16 | const change = await client.sendRequest<SourceChange>( |
16 | 'rust-analyzer/joinLines', | 17 | 'rust-analyzer/joinLines', |
17 | request, | 18 | request, |
18 | ); | 19 | ); |
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 393d6a602..13988056a 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts | |||
@@ -1,19 +1,38 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import { Server } from './server'; | ||
4 | import { Config } from './config'; | 3 | import { Config } from './config'; |
4 | import { createClient } from './client' | ||
5 | 5 | ||
6 | export class Ctx { | 6 | export class Ctx { |
7 | readonly config: Config; | 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 | ||
8 | private extCtx: vscode.ExtensionContext; | 14 | private extCtx: vscode.ExtensionContext; |
15 | private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = []; | ||
9 | 16 | ||
10 | constructor(extCtx: vscode.ExtensionContext) { | 17 | constructor(extCtx: vscode.ExtensionContext) { |
11 | this.config = new Config(extCtx) | 18 | this.config = new Config(extCtx) |
12 | this.extCtx = extCtx; | 19 | this.extCtx = extCtx; |
13 | } | 20 | } |
14 | 21 | ||
15 | get client(): lc.LanguageClient { | 22 | async restartServer() { |
16 | return Server.client; | 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 | } | ||
17 | } | 36 | } |
18 | 37 | ||
19 | get activeRustEditor(): vscode.TextEditor | undefined { | 38 | get activeRustEditor(): vscode.TextEditor | undefined { |
@@ -60,35 +79,34 @@ export class Ctx { | |||
60 | this.extCtx.subscriptions.push(d); | 79 | this.extCtx.subscriptions.push(d); |
61 | } | 80 | } |
62 | 81 | ||
63 | async sendRequestWithRetry<R>( | 82 | onDidRestart(hook: (client: lc.LanguageClient) => void) { |
64 | method: string, | 83 | this.onDidRestartHooks.push(hook) |
65 | param: any, | ||
66 | token?: vscode.CancellationToken, | ||
67 | ): Promise<R> { | ||
68 | await this.client.onReady(); | ||
69 | for (const delay of [2, 4, 6, 8, 10, null]) { | ||
70 | try { | ||
71 | return await (token ? this.client.sendRequest(method, param, token) : this.client.sendRequest(method, param)); | ||
72 | } catch (e) { | ||
73 | if ( | ||
74 | e.code === lc.ErrorCodes.ContentModified && | ||
75 | delay !== null | ||
76 | ) { | ||
77 | await sleep(10 * (1 << delay)); | ||
78 | continue; | ||
79 | } | ||
80 | throw e; | ||
81 | } | ||
82 | } | ||
83 | throw 'unreachable'; | ||
84 | } | ||
85 | |||
86 | onNotification(method: string, handler: lc.GenericNotificationHandler) { | ||
87 | this.client.onReady() | ||
88 | .then(() => this.client.onNotification(method, handler)) | ||
89 | } | 84 | } |
90 | } | 85 | } |
91 | 86 | ||
92 | export type Cmd = (...args: any[]) => any; | 87 | export type Cmd = (...args: any[]) => any; |
93 | 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 | |||
94 | const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); | 112 | const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); |
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index d4e961b5b..f9d2e9d90 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts | |||
@@ -5,31 +5,32 @@ const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular | |||
5 | 5 | ||
6 | import { ColorTheme, TextMateRuleSettings } from './color_theme'; | 6 | import { ColorTheme, TextMateRuleSettings } from './color_theme'; |
7 | 7 | ||
8 | import { Ctx } from './ctx'; | 8 | import { Ctx, sendRequestWithRetry } from './ctx'; |
9 | 9 | ||
10 | export function activateHighlighting(ctx: Ctx) { | 10 | export function activateHighlighting(ctx: Ctx) { |
11 | const highlighter = new Highlighter(ctx); | 11 | const highlighter = new Highlighter(ctx); |
12 | 12 | ctx.onDidRestart(client => { | |
13 | ctx.onNotification( | 13 | client.onNotification( |
14 | 'rust-analyzer/publishDecorations', | 14 | 'rust-analyzer/publishDecorations', |
15 | (params: PublishDecorationsParams) => { | 15 | (params: PublishDecorationsParams) => { |
16 | if (!ctx.config.highlightingOn) return; | 16 | if (!ctx.config.highlightingOn) return; |
17 | 17 | ||
18 | const targetEditor = vscode.window.visibleTextEditors.find( | 18 | const targetEditor = vscode.window.visibleTextEditors.find( |
19 | editor => { | 19 | editor => { |
20 | const unescapedUri = unescape( | 20 | const unescapedUri = unescape( |
21 | editor.document.uri.toString(), | 21 | editor.document.uri.toString(), |
22 | ); | 22 | ); |
23 | // Unescaped URI looks like: | 23 | // Unescaped URI looks like: |
24 | // file:///c:/Workspace/ra-test/src/main.rs | 24 | // file:///c:/Workspace/ra-test/src/main.rs |
25 | return unescapedUri === params.uri; | 25 | return unescapedUri === params.uri; |
26 | }, | 26 | }, |
27 | ); | 27 | ); |
28 | if (!targetEditor) return; | 28 | if (!targetEditor) return; |
29 | 29 | ||
30 | highlighter.setHighlights(targetEditor, params.decorations); | 30 | highlighter.setHighlights(targetEditor, params.decorations); |
31 | }, | 31 | }, |
32 | ); | 32 | ); |
33 | }) | ||
33 | 34 | ||
34 | vscode.workspace.onDidChangeConfiguration( | 35 | vscode.workspace.onDidChangeConfiguration( |
35 | _ => highlighter.removeHighlights(), | 36 | _ => highlighter.removeHighlights(), |
@@ -40,11 +41,14 @@ export function activateHighlighting(ctx: Ctx) { | |||
40 | async (editor: vscode.TextEditor | undefined) => { | 41 | async (editor: vscode.TextEditor | undefined) => { |
41 | if (!editor || editor.document.languageId !== 'rust') return; | 42 | if (!editor || editor.document.languageId !== 'rust') return; |
42 | if (!ctx.config.highlightingOn) return; | 43 | if (!ctx.config.highlightingOn) return; |
44 | let client = ctx.client; | ||
45 | if (!client) return; | ||
43 | 46 | ||
44 | const params: lc.TextDocumentIdentifier = { | 47 | const params: lc.TextDocumentIdentifier = { |
45 | uri: editor.document.uri.toString(), | 48 | uri: editor.document.uri.toString(), |
46 | }; | 49 | }; |
47 | const decorations = await ctx.sendRequestWithRetry<Decoration[]>( | 50 | const decorations = await sendRequestWithRetry<Decoration[]>( |
51 | client, | ||
48 | 'rust-analyzer/decorationsRequest', | 52 | 'rust-analyzer/decorationsRequest', |
49 | params, | 53 | params, |
50 | ); | 54 | ); |
@@ -103,6 +107,8 @@ class Highlighter { | |||
103 | } | 107 | } |
104 | 108 | ||
105 | public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) { | 109 | public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) { |
110 | let client = this.ctx.client; | ||
111 | if (!client) return; | ||
106 | // Initialize decorations if necessary | 112 | // Initialize decorations if necessary |
107 | // | 113 | // |
108 | // Note: decoration objects need to be kept around so we can dispose them | 114 | // Note: decoration objects need to be kept around so we can dispose them |
@@ -135,13 +141,13 @@ class Highlighter { | |||
135 | colorfulIdents | 141 | colorfulIdents |
136 | .get(d.bindingHash)![0] | 142 | .get(d.bindingHash)![0] |
137 | .push( | 143 | .push( |
138 | this.ctx.client.protocol2CodeConverter.asRange(d.range), | 144 | client.protocol2CodeConverter.asRange(d.range), |
139 | ); | 145 | ); |
140 | } else { | 146 | } else { |
141 | byTag | 147 | byTag |
142 | .get(d.tag)! | 148 | .get(d.tag)! |
143 | .push( | 149 | .push( |
144 | this.ctx.client.protocol2CodeConverter.asRange(d.range), | 150 | client.protocol2CodeConverter.asRange(d.range), |
145 | ); | 151 | ); |
146 | } | 152 | } |
147 | } | 153 | } |
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index b6eb70168..e74d6996f 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | 3 | ||
4 | import { Ctx } from './ctx'; | 4 | import { Ctx, sendRequestWithRetry } from './ctx'; |
5 | 5 | ||
6 | export function activateInlayHints(ctx: Ctx) { | 6 | export function activateInlayHints(ctx: Ctx) { |
7 | const hintsUpdater = new HintsUpdater(ctx); | 7 | const hintsUpdater = new HintsUpdater(ctx); |
@@ -19,9 +19,7 @@ export function activateInlayHints(ctx: Ctx) { | |||
19 | hintsUpdater.setEnabled(ctx.config.displayInlayHints); | 19 | hintsUpdater.setEnabled(ctx.config.displayInlayHints); |
20 | }, ctx.subscriptions); | 20 | }, ctx.subscriptions); |
21 | 21 | ||
22 | // XXX: don't await here; | 22 | ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints)) |
23 | // Who knows what happens if an exception is thrown here... | ||
24 | hintsUpdater.refresh(); | ||
25 | } | 23 | } |
26 | 24 | ||
27 | interface InlayHintsParams { | 25 | interface InlayHintsParams { |
@@ -97,6 +95,8 @@ class HintsUpdater { | |||
97 | } | 95 | } |
98 | 96 | ||
99 | private async queryHints(documentUri: string): Promise<InlayHint[] | null> { | 97 | private async queryHints(documentUri: string): Promise<InlayHint[] | null> { |
98 | let client = this.ctx.client; | ||
99 | if (!client) return null | ||
100 | const request: InlayHintsParams = { | 100 | const request: InlayHintsParams = { |
101 | textDocument: { uri: documentUri }, | 101 | textDocument: { uri: documentUri }, |
102 | }; | 102 | }; |
@@ -105,7 +105,8 @@ class HintsUpdater { | |||
105 | if (prev) prev.cancel(); | 105 | if (prev) prev.cancel(); |
106 | this.pending.set(documentUri, tokenSource); | 106 | this.pending.set(documentUri, tokenSource); |
107 | try { | 107 | try { |
108 | return await this.ctx.sendRequestWithRetry<InlayHint[] | null>( | 108 | return await sendRequestWithRetry<InlayHint[] | null>( |
109 | client, | ||
109 | 'rust-analyzer/inlayHints', | 110 | 'rust-analyzer/inlayHints', |
110 | request, | 111 | request, |
111 | tokenSource.token, | 112 | tokenSource.token, |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 3d9107927..22450060b 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -3,7 +3,6 @@ import * as vscode from 'vscode'; | |||
3 | import * as commands from './commands'; | 3 | import * as commands from './commands'; |
4 | import { activateInlayHints } from './inlay_hints'; | 4 | import { activateInlayHints } from './inlay_hints'; |
5 | import { activateStatusDisplay } from './status_display'; | 5 | import { activateStatusDisplay } from './status_display'; |
6 | import { Server } from './server'; | ||
7 | import { Ctx } from './ctx'; | 6 | import { Ctx } from './ctx'; |
8 | import { activateHighlighting } from './highlighting'; | 7 | import { activateHighlighting } from './highlighting'; |
9 | 8 | ||
@@ -21,6 +20,7 @@ export async function activate(context: vscode.ExtensionContext) { | |||
21 | ctx.registerCommand('syntaxTree', commands.syntaxTree); | 20 | ctx.registerCommand('syntaxTree', commands.syntaxTree); |
22 | ctx.registerCommand('expandMacro', commands.expandMacro); | 21 | ctx.registerCommand('expandMacro', commands.expandMacro); |
23 | ctx.registerCommand('run', commands.run); | 22 | ctx.registerCommand('run', commands.run); |
23 | ctx.registerCommand('reload', commands.reload); | ||
24 | 24 | ||
25 | // Internal commands which are invoked by the server. | 25 | // Internal commands which are invoked by the server. |
26 | ctx.registerCommand('runSingle', commands.runSingle); | 26 | ctx.registerCommand('runSingle', commands.runSingle); |
@@ -30,38 +30,17 @@ export async function activate(context: vscode.ExtensionContext) { | |||
30 | if (ctx.config.enableEnhancedTyping) { | 30 | if (ctx.config.enableEnhancedTyping) { |
31 | ctx.overrideCommand('type', commands.onEnter); | 31 | ctx.overrideCommand('type', commands.onEnter); |
32 | } | 32 | } |
33 | 33 | activateStatusDisplay(ctx); | |
34 | const startServer = () => Server.start(ctx.config); | 34 | activateHighlighting(ctx); |
35 | const reloadCommand = () => reloadServer(startServer); | 35 | activateInlayHints(ctx); |
36 | |||
37 | vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); | ||
38 | |||
39 | // Start the language server, finally! | 36 | // Start the language server, finally! |
40 | try { | 37 | try { |
41 | await startServer(); | 38 | await ctx.restartServer(); |
42 | } catch (e) { | 39 | } catch (e) { |
43 | vscode.window.showErrorMessage(e.message); | 40 | vscode.window.showErrorMessage(e.message); |
44 | } | 41 | } |
45 | |||
46 | activateStatusDisplay(ctx); | ||
47 | activateHighlighting(ctx); | ||
48 | |||
49 | if (ctx.config.displayInlayHints) { | ||
50 | activateInlayHints(ctx); | ||
51 | } | ||
52 | } | 42 | } |
53 | 43 | ||
54 | export function deactivate(): Thenable<void> { | 44 | export async function deactivate() { |
55 | if (!Server.client) { | 45 | await ctx?.client?.stop(); |
56 | return Promise.resolve(); | ||
57 | } | ||
58 | return Server.client.stop(); | ||
59 | } | ||
60 | |||
61 | async function reloadServer(startServer: () => Promise<void>) { | ||
62 | if (Server.client != null) { | ||
63 | vscode.window.showInformationMessage('Reloading rust-analyzer...'); | ||
64 | await Server.client.stop(); | ||
65 | await startServer(); | ||
66 | } | ||
67 | } | 46 | } |
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts deleted file mode 100644 index ab9f3bfa6..000000000 --- a/editors/code/src/server.ts +++ /dev/null | |||
@@ -1,96 +0,0 @@ | |||
1 | import { homedir } from 'os'; | ||
2 | import * as lc from 'vscode-languageclient'; | ||
3 | |||
4 | import { window, workspace } from 'vscode'; | ||
5 | import { Config } from './config'; | ||
6 | |||
7 | function expandPathResolving(path: string) { | ||
8 | if (path.startsWith('~/')) { | ||
9 | return path.replace('~', homedir()); | ||
10 | } | ||
11 | return path; | ||
12 | } | ||
13 | |||
14 | export class Server { | ||
15 | static config: Config; | ||
16 | public static client: lc.LanguageClient; | ||
17 | |||
18 | public static async start(config: Config) { | ||
19 | // '.' Is the fallback if no folder is open | ||
20 | // TODO?: Workspace folders support Uri's (eg: file://test.txt). It might be a good idea to test if the uri points to a file. | ||
21 | let folder: string = '.'; | ||
22 | if (workspace.workspaceFolders !== undefined) { | ||
23 | folder = workspace.workspaceFolders[0].uri.fsPath.toString(); | ||
24 | } | ||
25 | |||
26 | this.config = config; | ||
27 | const command = expandPathResolving(this.config.raLspServerPath); | ||
28 | const run: lc.Executable = { | ||
29 | command, | ||
30 | options: { cwd: folder }, | ||
31 | }; | ||
32 | const serverOptions: lc.ServerOptions = { | ||
33 | run, | ||
34 | debug: run, | ||
35 | }; | ||
36 | const traceOutputChannel = window.createOutputChannel( | ||
37 | 'Rust Analyzer Language Server Trace', | ||
38 | ); | ||
39 | const clientOptions: lc.LanguageClientOptions = { | ||
40 | documentSelector: [{ scheme: 'file', language: 'rust' }], | ||
41 | initializationOptions: { | ||
42 | publishDecorations: true, | ||
43 | lruCapacity: Server.config.lruCapacity, | ||
44 | maxInlayHintLength: Server.config.maxInlayHintLength, | ||
45 | cargoWatchEnable: Server.config.cargoWatchOptions.enable, | ||
46 | cargoWatchArgs: Server.config.cargoWatchOptions.arguments, | ||
47 | cargoWatchCommand: Server.config.cargoWatchOptions.command, | ||
48 | cargoWatchAllTargets: | ||
49 | Server.config.cargoWatchOptions.allTargets, | ||
50 | excludeGlobs: Server.config.excludeGlobs, | ||
51 | useClientWatching: Server.config.useClientWatching, | ||
52 | featureFlags: Server.config.featureFlags, | ||
53 | withSysroot: Server.config.withSysroot, | ||
54 | cargoFeatures: Server.config.cargoFeatures, | ||
55 | }, | ||
56 | traceOutputChannel, | ||
57 | }; | ||
58 | |||
59 | Server.client = new lc.LanguageClient( | ||
60 | 'rust-analyzer', | ||
61 | 'Rust Analyzer Language Server', | ||
62 | serverOptions, | ||
63 | clientOptions, | ||
64 | ); | ||
65 | // HACK: This is an awful way of filtering out the decorations notifications | ||
66 | // However, pending proper support, this is the most effecitve approach | ||
67 | // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages | ||
68 | // Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting) | ||
69 | // This also requires considering our settings strategy, which is work which needs doing | ||
70 | // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests | ||
71 | Server.client._tracer = { | ||
72 | log: (messageOrDataObject: string | any, data?: string) => { | ||
73 | if (typeof messageOrDataObject === 'string') { | ||
74 | if ( | ||
75 | messageOrDataObject.includes( | ||
76 | 'rust-analyzer/publishDecorations', | ||
77 | ) || | ||
78 | messageOrDataObject.includes( | ||
79 | 'rust-analyzer/decorationsRequest', | ||
80 | ) | ||
81 | ) { | ||
82 | // Don't log publish decorations requests | ||
83 | } else { | ||
84 | // @ts-ignore This is just a utility function | ||
85 | Server.client.logTrace(messageOrDataObject, data); | ||
86 | } | ||
87 | } else { | ||
88 | // @ts-ignore | ||
89 | Server.client.logObjectTrace(messageOrDataObject); | ||
90 | } | ||
91 | }, | ||
92 | }; | ||
93 | Server.client.registerProposedFeatures(); | ||
94 | Server.client.start(); | ||
95 | } | ||
96 | } | ||
diff --git a/editors/code/src/source_change.ts b/editors/code/src/source_change.ts index a4f9068b2..887191d9e 100644 --- a/editors/code/src/source_change.ts +++ b/editors/code/src/source_change.ts | |||
@@ -10,7 +10,10 @@ export interface SourceChange { | |||
10 | } | 10 | } |
11 | 11 | ||
12 | export async function applySourceChange(ctx: Ctx, change: SourceChange) { | 12 | export async function applySourceChange(ctx: Ctx, change: SourceChange) { |
13 | const wsEdit = ctx.client.protocol2CodeConverter.asWorkspaceEdit( | 13 | const client = ctx.client; |
14 | if (!client) return | ||
15 | |||
16 | const wsEdit = client.protocol2CodeConverter.asWorkspaceEdit( | ||
14 | change.workspaceEdit, | 17 | change.workspaceEdit, |
15 | ); | 18 | ); |
16 | let created; | 19 | let created; |
@@ -32,10 +35,10 @@ export async function applySourceChange(ctx: Ctx, change: SourceChange) { | |||
32 | const doc = await vscode.workspace.openTextDocument(toOpenUri); | 35 | const doc = await vscode.workspace.openTextDocument(toOpenUri); |
33 | await vscode.window.showTextDocument(doc); | 36 | await vscode.window.showTextDocument(doc); |
34 | } else if (toReveal) { | 37 | } else if (toReveal) { |
35 | const uri = ctx.client.protocol2CodeConverter.asUri( | 38 | const uri = client.protocol2CodeConverter.asUri( |
36 | toReveal.textDocument.uri, | 39 | toReveal.textDocument.uri, |
37 | ); | 40 | ); |
38 | const position = ctx.client.protocol2CodeConverter.asPosition( | 41 | const position = client.protocol2CodeConverter.asPosition( |
39 | toReveal.position, | 42 | toReveal.position, |
40 | ); | 43 | ); |
41 | const editor = vscode.window.activeTextEditor; | 44 | const editor = vscode.window.activeTextEditor; |
diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts index e3719075b..1454bf8b0 100644 --- a/editors/code/src/status_display.ts +++ b/editors/code/src/status_display.ts | |||
@@ -7,7 +7,9 @@ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', ' | |||
7 | export function activateStatusDisplay(ctx: Ctx) { | 7 | export function activateStatusDisplay(ctx: Ctx) { |
8 | const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); | 8 | const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); |
9 | ctx.pushCleanup(statusDisplay); | 9 | ctx.pushCleanup(statusDisplay); |
10 | ctx.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params)); | 10 | ctx.onDidRestart(client => { |
11 | client.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params)); | ||
12 | }) | ||
11 | } | 13 | } |
12 | 14 | ||
13 | class StatusDisplay implements vscode.Disposable { | 15 | class StatusDisplay implements vscode.Disposable { |