diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2019-12-31 17:38:55 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2019-12-31 17:38:55 +0000 |
commit | 6d23140ba03c77b28d94e042c94155899baba9da (patch) | |
tree | 3efa5daf54fe08cd1b310fa42c2ef469503fcedd /editors | |
parent | 1327aed7f6289043091aa9179282030c6f13ddbe (diff) | |
parent | 6368b40dd98b208da3758d4d1eed34cf276e3b09 (diff) |
Merge #2709
2709: Work around synchrnonisation issue r=matklad a=matklad
Co-authored-by: Aleksey Kladov <[email protected]>
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/config.ts | 36 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 75 | ||||
-rw-r--r-- | editors/code/src/highlighting.ts | 20 | ||||
-rw-r--r-- | editors/code/src/inlay_hints.ts | 11 | ||||
-rw-r--r-- | editors/code/src/main.ts | 61 | ||||
-rw-r--r-- | editors/code/src/server.ts | 102 | ||||
-rw-r--r-- | editors/code/src/source_change.ts | 9 | ||||
-rw-r--r-- | editors/code/src/status_display.ts | 12 |
13 files changed, 235 insertions, 223 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/config.ts b/editors/code/src/config.ts index ccb0ee2b7..ec2790b63 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -16,25 +16,25 @@ export interface CargoFeatures { | |||
16 | } | 16 | } |
17 | 17 | ||
18 | export class Config { | 18 | export class Config { |
19 | public highlightingOn = true; | 19 | highlightingOn = true; |
20 | public rainbowHighlightingOn = false; | 20 | rainbowHighlightingOn = false; |
21 | public enableEnhancedTyping = true; | 21 | enableEnhancedTyping = true; |
22 | public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; | 22 | raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; |
23 | public lruCapacity: null | number = null; | 23 | lruCapacity: null | number = null; |
24 | public displayInlayHints = true; | 24 | displayInlayHints = true; |
25 | public maxInlayHintLength: null | number = null; | 25 | maxInlayHintLength: null | number = null; |
26 | public excludeGlobs = []; | 26 | excludeGlobs = []; |
27 | public useClientWatching = true; | 27 | useClientWatching = true; |
28 | public featureFlags = {}; | 28 | featureFlags = {}; |
29 | // for internal use | 29 | // for internal use |
30 | public withSysroot: null | boolean = null; | 30 | withSysroot: null | boolean = null; |
31 | public cargoWatchOptions: CargoWatchOptions = { | 31 | cargoWatchOptions: CargoWatchOptions = { |
32 | enable: true, | 32 | enable: true, |
33 | arguments: [], | 33 | arguments: [], |
34 | command: '', | 34 | command: '', |
35 | allTargets: true, | 35 | allTargets: true, |
36 | }; | 36 | }; |
37 | public cargoFeatures: CargoFeatures = { | 37 | cargoFeatures: CargoFeatures = { |
38 | noDefaultFeatures: false, | 38 | noDefaultFeatures: false, |
39 | allFeatures: true, | 39 | allFeatures: true, |
40 | features: [], | 40 | features: [], |
@@ -43,14 +43,12 @@ export class Config { | |||
43 | private prevEnhancedTyping: null | boolean = null; | 43 | private prevEnhancedTyping: null | boolean = null; |
44 | private prevCargoFeatures: null | CargoFeatures = null; | 44 | private prevCargoFeatures: null | CargoFeatures = null; |
45 | 45 | ||
46 | constructor() { | 46 | constructor(ctx: vscode.ExtensionContext) { |
47 | vscode.workspace.onDidChangeConfiguration(_ => | 47 | vscode.workspace.onDidChangeConfiguration(_ => this.refresh(), ctx.subscriptions); |
48 | this.userConfigChanged(), | 48 | this.refresh(); |
49 | ); | ||
50 | this.userConfigChanged(); | ||
51 | } | 49 | } |
52 | 50 | ||
53 | public userConfigChanged() { | 51 | private refresh() { |
54 | const config = vscode.workspace.getConfiguration('rust-analyzer'); | 52 | const config = vscode.workspace.getConfiguration('rust-analyzer'); |
55 | 53 | ||
56 | let requireReloadMessage = null; | 54 | let requireReloadMessage = null; |
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 693ce05ed..13988056a 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts | |||
@@ -1,21 +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; | ||
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 | ||
7 | private extCtx: vscode.ExtensionContext; | 14 | private extCtx: vscode.ExtensionContext; |
15 | private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = []; | ||
8 | 16 | ||
9 | constructor(extCtx: vscode.ExtensionContext) { | 17 | constructor(extCtx: vscode.ExtensionContext) { |
18 | this.config = new Config(extCtx) | ||
10 | this.extCtx = extCtx; | 19 | this.extCtx = extCtx; |
11 | } | 20 | } |
12 | 21 | ||
13 | get client(): lc.LanguageClient { | 22 | async restartServer() { |
14 | return Server.client; | 23 | let old = this.client; |
15 | } | 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(); | ||
16 | 31 | ||
17 | get config(): Config { | 32 | this.client = client |
18 | return Server.config; | 33 | for (const hook of this.onDidRestartHooks) { |
34 | hook(client) | ||
35 | } | ||
19 | } | 36 | } |
20 | 37 | ||
21 | get activeRustEditor(): vscode.TextEditor | undefined { | 38 | get activeRustEditor(): vscode.TextEditor | undefined { |
@@ -62,30 +79,34 @@ export class Ctx { | |||
62 | this.extCtx.subscriptions.push(d); | 79 | this.extCtx.subscriptions.push(d); |
63 | } | 80 | } |
64 | 81 | ||
65 | async sendRequestWithRetry<R>( | 82 | onDidRestart(hook: (client: lc.LanguageClient) => void) { |
66 | method: string, | 83 | this.onDidRestartHooks.push(hook) |
67 | param: any, | ||
68 | token?: vscode.CancellationToken, | ||
69 | ): Promise<R> { | ||
70 | await this.client.onReady(); | ||
71 | for (const delay of [2, 4, 6, 8, 10, null]) { | ||
72 | try { | ||
73 | return await (token ? this.client.sendRequest(method, param, token) : this.client.sendRequest(method, param)); | ||
74 | } catch (e) { | ||
75 | if ( | ||
76 | e.code === lc.ErrorCodes.ContentModified && | ||
77 | delay !== null | ||
78 | ) { | ||
79 | await sleep(10 * (1 << delay)); | ||
80 | continue; | ||
81 | } | ||
82 | throw e; | ||
83 | } | ||
84 | } | ||
85 | throw 'unreachable'; | ||
86 | } | 84 | } |
87 | } | 85 | } |
88 | 86 | ||
89 | export type Cmd = (...args: any[]) => any; | 87 | export type Cmd = (...args: any[]) => any; |
90 | 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 | |||
91 | 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 d383d87ef..f9d2e9d90 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts | |||
@@ -5,13 +5,12 @@ 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.client.onReady().then(() => { | 13 | client.onNotification( |
14 | ctx.client.onNotification( | ||
15 | 'rust-analyzer/publishDecorations', | 14 | 'rust-analyzer/publishDecorations', |
16 | (params: PublishDecorationsParams) => { | 15 | (params: PublishDecorationsParams) => { |
17 | if (!ctx.config.highlightingOn) return; | 16 | if (!ctx.config.highlightingOn) return; |
@@ -31,7 +30,7 @@ export function activateHighlighting(ctx: Ctx) { | |||
31 | highlighter.setHighlights(targetEditor, params.decorations); | 30 | highlighter.setHighlights(targetEditor, params.decorations); |
32 | }, | 31 | }, |
33 | ); | 32 | ); |
34 | }); | 33 | }) |
35 | 34 | ||
36 | vscode.workspace.onDidChangeConfiguration( | 35 | vscode.workspace.onDidChangeConfiguration( |
37 | _ => highlighter.removeHighlights(), | 36 | _ => highlighter.removeHighlights(), |
@@ -42,11 +41,14 @@ export function activateHighlighting(ctx: Ctx) { | |||
42 | async (editor: vscode.TextEditor | undefined) => { | 41 | async (editor: vscode.TextEditor | undefined) => { |
43 | if (!editor || editor.document.languageId !== 'rust') return; | 42 | if (!editor || editor.document.languageId !== 'rust') return; |
44 | if (!ctx.config.highlightingOn) return; | 43 | if (!ctx.config.highlightingOn) return; |
44 | let client = ctx.client; | ||
45 | if (!client) return; | ||
45 | 46 | ||
46 | const params: lc.TextDocumentIdentifier = { | 47 | const params: lc.TextDocumentIdentifier = { |
47 | uri: editor.document.uri.toString(), | 48 | uri: editor.document.uri.toString(), |
48 | }; | 49 | }; |
49 | const decorations = await ctx.sendRequestWithRetry<Decoration[]>( | 50 | const decorations = await sendRequestWithRetry<Decoration[]>( |
51 | client, | ||
50 | 'rust-analyzer/decorationsRequest', | 52 | 'rust-analyzer/decorationsRequest', |
51 | params, | 53 | params, |
52 | ); | 54 | ); |
@@ -105,6 +107,8 @@ class Highlighter { | |||
105 | } | 107 | } |
106 | 108 | ||
107 | 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; | ||
108 | // Initialize decorations if necessary | 112 | // Initialize decorations if necessary |
109 | // | 113 | // |
110 | // 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 |
@@ -137,13 +141,13 @@ class Highlighter { | |||
137 | colorfulIdents | 141 | colorfulIdents |
138 | .get(d.bindingHash)![0] | 142 | .get(d.bindingHash)![0] |
139 | .push( | 143 | .push( |
140 | this.ctx.client.protocol2CodeConverter.asRange(d.range), | 144 | client.protocol2CodeConverter.asRange(d.range), |
141 | ); | 145 | ); |
142 | } else { | 146 | } else { |
143 | byTag | 147 | byTag |
144 | .get(d.tag)! | 148 | .get(d.tag)! |
145 | .push( | 149 | .push( |
146 | this.ctx.client.protocol2CodeConverter.asRange(d.range), | 150 | client.protocol2CodeConverter.asRange(d.range), |
147 | ); | 151 | ); |
148 | } | 152 | } |
149 | } | 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 0c4abdac8..51dedd5ef 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -1,10 +1,8 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | ||
3 | 2 | ||
4 | import * as commands from './commands'; | 3 | import * as commands from './commands'; |
5 | import { activateInlayHints } from './inlay_hints'; | 4 | import { activateInlayHints } from './inlay_hints'; |
6 | import { StatusDisplay } from './status_display'; | 5 | import { activateStatusDisplay } from './status_display'; |
7 | import { Server } from './server'; | ||
8 | import { Ctx } from './ctx'; | 6 | import { Ctx } from './ctx'; |
9 | import { activateHighlighting } from './highlighting'; | 7 | import { activateHighlighting } from './highlighting'; |
10 | 8 | ||
@@ -13,6 +11,17 @@ let ctx!: Ctx; | |||
13 | export async function activate(context: vscode.ExtensionContext) { | 11 | export async function activate(context: vscode.ExtensionContext) { |
14 | ctx = new Ctx(context); | 12 | ctx = new Ctx(context); |
15 | 13 | ||
14 | // Note: we try to start the server before we register various commands, so | ||
15 | // that it registers its `onDidChangeDocument` handler before us. | ||
16 | // | ||
17 | // This a horribly, horribly wrong way to deal with this problem. | ||
18 | try { | ||
19 | await ctx.restartServer(); | ||
20 | } catch (e) { | ||
21 | vscode.window.showErrorMessage(e.message); | ||
22 | } | ||
23 | |||
24 | |||
16 | // Commands which invokes manually via command pallet, shortcut, etc. | 25 | // Commands which invokes manually via command pallet, shortcut, etc. |
17 | ctx.registerCommand('analyzerStatus', commands.analyzerStatus); | 26 | ctx.registerCommand('analyzerStatus', commands.analyzerStatus); |
18 | ctx.registerCommand('collectGarbage', commands.collectGarbage); | 27 | ctx.registerCommand('collectGarbage', commands.collectGarbage); |
@@ -22,6 +31,7 @@ export async function activate(context: vscode.ExtensionContext) { | |||
22 | ctx.registerCommand('syntaxTree', commands.syntaxTree); | 31 | ctx.registerCommand('syntaxTree', commands.syntaxTree); |
23 | ctx.registerCommand('expandMacro', commands.expandMacro); | 32 | ctx.registerCommand('expandMacro', commands.expandMacro); |
24 | ctx.registerCommand('run', commands.run); | 33 | ctx.registerCommand('run', commands.run); |
34 | ctx.registerCommand('reload', commands.reload); | ||
25 | 35 | ||
26 | // Internal commands which are invoked by the server. | 36 | // Internal commands which are invoked by the server. |
27 | ctx.registerCommand('runSingle', commands.runSingle); | 37 | ctx.registerCommand('runSingle', commands.runSingle); |
@@ -31,48 +41,11 @@ export async function activate(context: vscode.ExtensionContext) { | |||
31 | if (ctx.config.enableEnhancedTyping) { | 41 | if (ctx.config.enableEnhancedTyping) { |
32 | ctx.overrideCommand('type', commands.onEnter); | 42 | ctx.overrideCommand('type', commands.onEnter); |
33 | } | 43 | } |
34 | 44 | activateStatusDisplay(ctx); | |
35 | const watchStatus = new StatusDisplay(ctx.config.cargoWatchOptions.command); | ||
36 | ctx.pushCleanup(watchStatus); | ||
37 | |||
38 | // Notifications are events triggered by the language server | ||
39 | const allNotifications: [string, lc.GenericNotificationHandler][] = [ | ||
40 | [ | ||
41 | '$/progress', | ||
42 | params => watchStatus.handleProgressNotification(params), | ||
43 | ], | ||
44 | ]; | ||
45 | |||
46 | const startServer = () => Server.start(allNotifications); | ||
47 | const reloadCommand = () => reloadServer(startServer); | ||
48 | |||
49 | vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); | ||
50 | |||
51 | // Start the language server, finally! | ||
52 | try { | ||
53 | await startServer(); | ||
54 | } catch (e) { | ||
55 | vscode.window.showErrorMessage(e.message); | ||
56 | } | ||
57 | |||
58 | activateHighlighting(ctx); | 45 | activateHighlighting(ctx); |
59 | 46 | activateInlayHints(ctx); | |
60 | if (ctx.config.displayInlayHints) { | ||
61 | activateInlayHints(ctx); | ||
62 | } | ||
63 | } | 47 | } |
64 | 48 | ||
65 | export function deactivate(): Thenable<void> { | 49 | export async function deactivate() { |
66 | if (!Server.client) { | 50 | await ctx?.client?.stop(); |
67 | return Promise.resolve(); | ||
68 | } | ||
69 | return Server.client.stop(); | ||
70 | } | ||
71 | |||
72 | async function reloadServer(startServer: () => Promise<void>) { | ||
73 | if (Server.client != null) { | ||
74 | vscode.window.showInformationMessage('Reloading rust-analyzer...'); | ||
75 | await Server.client.stop(); | ||
76 | await startServer(); | ||
77 | } | ||
78 | } | 51 | } |
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts deleted file mode 100644 index 2bb21da6b..000000000 --- a/editors/code/src/server.ts +++ /dev/null | |||
@@ -1,102 +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 | public static config = new Config(); | ||
16 | public static client: lc.LanguageClient; | ||
17 | |||
18 | public static async start( | ||
19 | notificationHandlers: Iterable<[string, lc.GenericNotificationHandler]>, | ||
20 | ) { | ||
21 | // '.' Is the fallback if no folder is open | ||
22 | // 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. | ||
23 | let folder: string = '.'; | ||
24 | if (workspace.workspaceFolders !== undefined) { | ||
25 | folder = workspace.workspaceFolders[0].uri.fsPath.toString(); | ||
26 | } | ||
27 | |||
28 | const command = expandPathResolving(this.config.raLspServerPath); | ||
29 | const run: lc.Executable = { | ||
30 | command, | ||
31 | options: { cwd: folder }, | ||
32 | }; | ||
33 | const serverOptions: lc.ServerOptions = { | ||
34 | run, | ||
35 | debug: run, | ||
36 | }; | ||
37 | const traceOutputChannel = window.createOutputChannel( | ||
38 | 'Rust Analyzer Language Server Trace', | ||
39 | ); | ||
40 | const clientOptions: lc.LanguageClientOptions = { | ||
41 | documentSelector: [{ scheme: 'file', language: 'rust' }], | ||
42 | initializationOptions: { | ||
43 | publishDecorations: true, | ||
44 | lruCapacity: Server.config.lruCapacity, | ||
45 | maxInlayHintLength: Server.config.maxInlayHintLength, | ||
46 | cargoWatchEnable: Server.config.cargoWatchOptions.enable, | ||
47 | cargoWatchArgs: Server.config.cargoWatchOptions.arguments, | ||
48 | cargoWatchCommand: Server.config.cargoWatchOptions.command, | ||
49 | cargoWatchAllTargets: | ||
50 | Server.config.cargoWatchOptions.allTargets, | ||
51 | excludeGlobs: Server.config.excludeGlobs, | ||
52 | useClientWatching: Server.config.useClientWatching, | ||
53 | featureFlags: Server.config.featureFlags, | ||
54 | withSysroot: Server.config.withSysroot, | ||
55 | cargoFeatures: Server.config.cargoFeatures, | ||
56 | }, | ||
57 | traceOutputChannel, | ||
58 | }; | ||
59 | |||
60 | Server.client = new lc.LanguageClient( | ||
61 | 'rust-analyzer', | ||
62 | 'Rust Analyzer Language Server', | ||
63 | serverOptions, | ||
64 | clientOptions, | ||
65 | ); | ||
66 | // HACK: This is an awful way of filtering out the decorations notifications | ||
67 | // However, pending proper support, this is the most effecitve approach | ||
68 | // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages | ||
69 | // Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting) | ||
70 | // This also requires considering our settings strategy, which is work which needs doing | ||
71 | // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests | ||
72 | Server.client._tracer = { | ||
73 | log: (messageOrDataObject: string | any, data?: string) => { | ||
74 | if (typeof messageOrDataObject === 'string') { | ||
75 | if ( | ||
76 | messageOrDataObject.includes( | ||
77 | 'rust-analyzer/publishDecorations', | ||
78 | ) || | ||
79 | messageOrDataObject.includes( | ||
80 | 'rust-analyzer/decorationsRequest', | ||
81 | ) | ||
82 | ) { | ||
83 | // Don't log publish decorations requests | ||
84 | } else { | ||
85 | // @ts-ignore This is just a utility function | ||
86 | Server.client.logTrace(messageOrDataObject, data); | ||
87 | } | ||
88 | } else { | ||
89 | // @ts-ignore | ||
90 | Server.client.logObjectTrace(messageOrDataObject); | ||
91 | } | ||
92 | }, | ||
93 | }; | ||
94 | Server.client.registerProposedFeatures(); | ||
95 | Server.client.onReady().then(() => { | ||
96 | for (const [type, handler] of notificationHandlers) { | ||
97 | Server.client.onNotification(type, handler); | ||
98 | } | ||
99 | }); | ||
100 | Server.client.start(); | ||
101 | } | ||
102 | } | ||
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 48cf0655b..1454bf8b0 100644 --- a/editors/code/src/status_display.ts +++ b/editors/code/src/status_display.ts | |||
@@ -1,8 +1,18 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | 2 | ||
3 | import { Ctx } from './ctx'; | ||
4 | |||
3 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; | 5 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; |
4 | 6 | ||
5 | export class StatusDisplay implements vscode.Disposable { | 7 | export function activateStatusDisplay(ctx: Ctx) { |
8 | const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); | ||
9 | ctx.pushCleanup(statusDisplay); | ||
10 | ctx.onDidRestart(client => { | ||
11 | client.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params)); | ||
12 | }) | ||
13 | } | ||
14 | |||
15 | class StatusDisplay implements vscode.Disposable { | ||
6 | packageName?: string; | 16 | packageName?: string; |
7 | 17 | ||
8 | private i = 0; | 18 | private i = 0; |