diff options
-rw-r--r-- | crates/ra_lsp_server/src/main_loop.rs | 15 | ||||
-rw-r--r-- | editors/code/rollup.config.js | 2 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 23 | ||||
-rw-r--r-- | editors/code/src/events/change_active_text_editor.ts | 25 | ||||
-rw-r--r-- | editors/code/src/events/index.ts | 3 | ||||
-rw-r--r-- | editors/code/src/highlighting.ts | 23 | ||||
-rw-r--r-- | editors/code/src/inlay_hints.ts | 176 | ||||
-rw-r--r-- | editors/code/src/main.ts | 17 |
8 files changed, 130 insertions, 154 deletions
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index af1a487de..4336583fe 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs | |||
@@ -709,16 +709,11 @@ where | |||
709 | Ok(lsp_error) => Response::new_err(id, lsp_error.code, lsp_error.message), | 709 | Ok(lsp_error) => Response::new_err(id, lsp_error.code, lsp_error.message), |
710 | Err(e) => { | 710 | Err(e) => { |
711 | if is_canceled(&e) { | 711 | if is_canceled(&e) { |
712 | // FIXME: When https://github.com/Microsoft/vscode-languageserver-node/issues/457 | 712 | Response::new_err( |
713 | // gets fixed, we can return the proper response. | 713 | id, |
714 | // This works around the issue where "content modified" error would continuously | 714 | ErrorCode::ContentModified as i32, |
715 | // show an message pop-up in VsCode | 715 | "content modified".to_string(), |
716 | // Response::err( | 716 | ) |
717 | // id, | ||
718 | // ErrorCode::ContentModified as i32, | ||
719 | // "content modified".to_string(), | ||
720 | // ) | ||
721 | Response::new_ok(id, ()) | ||
722 | } else { | 717 | } else { |
723 | Response::new_err(id, ErrorCode::InternalError as i32, e.to_string()) | 718 | Response::new_err(id, ErrorCode::InternalError as i32, e.to_string()) |
724 | } | 719 | } |
diff --git a/editors/code/rollup.config.js b/editors/code/rollup.config.js index 4c001f899..14fb9e085 100644 --- a/editors/code/rollup.config.js +++ b/editors/code/rollup.config.js | |||
@@ -13,7 +13,7 @@ export default { | |||
13 | commonjs({ | 13 | commonjs({ |
14 | namedExports: { | 14 | namedExports: { |
15 | // squelch missing import warnings | 15 | // squelch missing import warnings |
16 | 'vscode-languageclient': ['CreateFile', 'RenameFile'] | 16 | 'vscode-languageclient': ['CreateFile', 'RenameFile', 'ErrorCodes'] |
17 | } | 17 | } |
18 | }) | 18 | }) |
19 | ], | 19 | ], |
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index c3a3583b5..d3ef27e43 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts | |||
@@ -1,6 +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 | import { Server } from './server'; | 3 | import { Server } from './server'; |
4 | import { Config } from './config'; | ||
4 | 5 | ||
5 | export class Ctx { | 6 | export class Ctx { |
6 | private extCtx: vscode.ExtensionContext; | 7 | private extCtx: vscode.ExtensionContext; |
@@ -13,6 +14,10 @@ export class Ctx { | |||
13 | return Server.client; | 14 | return Server.client; |
14 | } | 15 | } |
15 | 16 | ||
17 | get config(): Config { | ||
18 | return Server.config; | ||
19 | } | ||
20 | |||
16 | get activeRustEditor(): vscode.TextEditor | undefined { | 21 | get activeRustEditor(): vscode.TextEditor | undefined { |
17 | const editor = vscode.window.activeTextEditor; | 22 | const editor = vscode.window.activeTextEditor; |
18 | return editor && editor.document.languageId === 'rust' | 23 | return editor && editor.document.languageId === 'rust' |
@@ -56,6 +61,24 @@ export class Ctx { | |||
56 | pushCleanup(d: { dispose(): any }) { | 61 | pushCleanup(d: { dispose(): any }) { |
57 | this.extCtx.subscriptions.push(d); | 62 | this.extCtx.subscriptions.push(d); |
58 | } | 63 | } |
64 | |||
65 | async sendRequestWithRetry<R>(method: string, param: any, token: vscode.CancellationToken): Promise<R> { | ||
66 | await this.client.onReady(); | ||
67 | for (const delay of [2, 4, 6, 8, 10, null]) { | ||
68 | try { | ||
69 | return await this.client.sendRequest(method, param, token); | ||
70 | } catch (e) { | ||
71 | if (e.code === lc.ErrorCodes.ContentModified && delay !== null) { | ||
72 | await sleep(10 * (1 << delay)) | ||
73 | continue; | ||
74 | } | ||
75 | throw e; | ||
76 | } | ||
77 | } | ||
78 | throw 'unreachable' | ||
79 | } | ||
59 | } | 80 | } |
60 | 81 | ||
61 | export type Cmd = (...args: any[]) => any; | 82 | export type Cmd = (...args: any[]) => any; |
83 | |||
84 | const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) | ||
diff --git a/editors/code/src/events/change_active_text_editor.ts b/editors/code/src/events/change_active_text_editor.ts deleted file mode 100644 index 4384ee567..000000000 --- a/editors/code/src/events/change_active_text_editor.ts +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | import { TextEditor } from 'vscode'; | ||
2 | import { TextDocumentIdentifier } from 'vscode-languageclient'; | ||
3 | import { Decoration } from '../highlighting'; | ||
4 | import { Server } from '../server'; | ||
5 | |||
6 | export function makeHandler() { | ||
7 | return async function handle(editor: TextEditor | undefined) { | ||
8 | if (!editor || editor.document.languageId !== 'rust') { | ||
9 | return; | ||
10 | } | ||
11 | |||
12 | if (!Server.config.highlightingOn) { | ||
13 | return; | ||
14 | } | ||
15 | |||
16 | const params: TextDocumentIdentifier = { | ||
17 | uri: editor.document.uri.toString(), | ||
18 | }; | ||
19 | const decorations = await Server.client.sendRequest<Decoration[]>( | ||
20 | 'rust-analyzer/decorationsRequest', | ||
21 | params, | ||
22 | ); | ||
23 | Server.highlighter.setHighlights(editor, decorations); | ||
24 | }; | ||
25 | } | ||
diff --git a/editors/code/src/events/index.ts b/editors/code/src/events/index.ts deleted file mode 100644 index be135474d..000000000 --- a/editors/code/src/events/index.ts +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | import * as changeActiveTextEditor from './change_active_text_editor'; | ||
2 | |||
3 | export { changeActiveTextEditor }; | ||
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index 4e224a54c..333319b85 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts | |||
@@ -1,10 +1,31 @@ | |||
1 | import seedrandom = require('seedrandom'); | ||
2 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
3 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import * as seedrandom_ from 'seedrandom'; | ||
4 | const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular2-library/issues/221#issuecomment-355945207 | ||
5 | |||
4 | import * as scopes from './scopes'; | 6 | import * as scopes from './scopes'; |
5 | import * as scopesMapper from './scopes_mapper'; | 7 | import * as scopesMapper from './scopes_mapper'; |
6 | 8 | ||
7 | import { Server } from './server'; | 9 | import { Server } from './server'; |
10 | import { Ctx } from './ctx'; | ||
11 | |||
12 | export function activateHighlighting(ctx: Ctx) { | ||
13 | vscode.window.onDidChangeActiveTextEditor( | ||
14 | async (editor: vscode.TextEditor | undefined) => { | ||
15 | if (!editor || editor.document.languageId !== 'rust') return; | ||
16 | if (!ctx.config.highlightingOn) return; | ||
17 | |||
18 | const params: lc.TextDocumentIdentifier = { | ||
19 | uri: editor.document.uri.toString(), | ||
20 | }; | ||
21 | const decorations = await ctx.client.sendRequest<Decoration[]>( | ||
22 | 'rust-analyzer/decorationsRequest', | ||
23 | params, | ||
24 | ); | ||
25 | Server.highlighter.setHighlights(editor, decorations); | ||
26 | }, | ||
27 | ); | ||
28 | } | ||
8 | 29 | ||
9 | export interface Decoration { | 30 | export interface Decoration { |
10 | range: lc.Range; | 31 | range: lc.Range; |
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 4581e2278..d41297407 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts | |||
@@ -1,41 +1,27 @@ | |||
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'; | 3 | |
4 | import { Ctx } from './ctx'; | 4 | import { Ctx } from './ctx'; |
5 | 5 | ||
6 | export function activateInlayHints(ctx: Ctx) { | 6 | export function activateInlayHints(ctx: Ctx) { |
7 | const hintsUpdater = new HintsUpdater(); | 7 | const hintsUpdater = new HintsUpdater(ctx); |
8 | hintsUpdater.refreshHintsForVisibleEditors().then(() => { | 8 | vscode.window.onDidChangeVisibleTextEditors(async _ => { |
9 | // vscode may ignore top level hintsUpdater.refreshHintsForVisibleEditors() | 9 | await hintsUpdater.refresh(); |
10 | // so update the hints once when the focus changes to guarantee their presence | 10 | }, ctx.subscriptions); |
11 | let editorChangeDisposable: vscode.Disposable | null = null; | ||
12 | editorChangeDisposable = vscode.window.onDidChangeActiveTextEditor( | ||
13 | _ => { | ||
14 | if (editorChangeDisposable !== null) { | ||
15 | editorChangeDisposable.dispose(); | ||
16 | } | ||
17 | return hintsUpdater.refreshHintsForVisibleEditors(); | ||
18 | }, | ||
19 | ); | ||
20 | 11 | ||
21 | ctx.pushCleanup( | 12 | vscode.workspace.onDidChangeTextDocument(async e => { |
22 | vscode.window.onDidChangeVisibleTextEditors(_ => | 13 | if (e.contentChanges.length === 0) return; |
23 | hintsUpdater.refreshHintsForVisibleEditors(), | 14 | if (e.document.languageId !== 'rust') return; |
24 | ), | 15 | await hintsUpdater.refresh(); |
25 | ); | 16 | }, ctx.subscriptions); |
26 | ctx.pushCleanup( | 17 | |
27 | vscode.workspace.onDidChangeTextDocument(e => | 18 | vscode.workspace.onDidChangeConfiguration(_ => { |
28 | hintsUpdater.refreshHintsForVisibleEditors(e), | 19 | hintsUpdater.setEnabled(ctx.config.displayInlayHints); |
29 | ), | 20 | }, ctx.subscriptions); |
30 | ); | 21 | |
31 | ctx.pushCleanup( | 22 | // XXX: don't await here; |
32 | vscode.workspace.onDidChangeConfiguration(_ => | 23 | // Who knows what happens if an exception is thrown here... |
33 | hintsUpdater.toggleHintsDisplay( | 24 | hintsUpdater.refresh(); |
34 | Server.config.displayInlayHints, | ||
35 | ), | ||
36 | ), | ||
37 | ); | ||
38 | }); | ||
39 | } | 25 | } |
40 | 26 | ||
41 | interface InlayHintsParams { | 27 | interface InlayHintsParams { |
@@ -55,95 +41,79 @@ const typeHintDecorationType = vscode.window.createTextEditorDecorationType({ | |||
55 | }); | 41 | }); |
56 | 42 | ||
57 | class HintsUpdater { | 43 | class HintsUpdater { |
58 | private displayHints = true; | 44 | private pending: Map<string, vscode.CancellationTokenSource> = new Map(); |
45 | private ctx: Ctx; | ||
46 | private enabled = true; | ||
59 | 47 | ||
60 | public async toggleHintsDisplay(displayHints: boolean): Promise<void> { | 48 | constructor(ctx: Ctx) { |
61 | if (this.displayHints !== displayHints) { | 49 | this.ctx = ctx; |
62 | this.displayHints = displayHints; | ||
63 | return this.refreshVisibleEditorsHints( | ||
64 | displayHints ? undefined : [], | ||
65 | ); | ||
66 | } | ||
67 | } | 50 | } |
68 | 51 | ||
69 | public async refreshHintsForVisibleEditors( | 52 | async setEnabled(enabled: boolean) { |
70 | cause?: vscode.TextDocumentChangeEvent, | 53 | if (this.enabled == enabled) return; |
71 | ): Promise<void> { | 54 | this.enabled = enabled; |
72 | if (!this.displayHints) return; | 55 | |
73 | 56 | if (this.enabled) { | |
74 | if ( | 57 | await this.refresh(); |
75 | cause !== undefined && | 58 | } else { |
76 | (cause.contentChanges.length === 0 || | 59 | this.allEditors.forEach(it => this.setDecorations(it, [])); |
77 | !this.isRustDocument(cause.document)) | ||
78 | ) { | ||
79 | return; | ||
80 | } | 60 | } |
81 | return this.refreshVisibleEditorsHints(); | ||
82 | } | 61 | } |
83 | 62 | ||
84 | private async refreshVisibleEditorsHints( | 63 | async refresh() { |
85 | newDecorations?: vscode.DecorationOptions[], | 64 | if (!this.enabled) return; |
86 | ) { | 65 | const promises = this.allEditors.map(it => this.refreshEditor(it)); |
87 | const promises: Array<Promise<void>> = []; | 66 | await Promise.all(promises); |
88 | 67 | } | |
89 | for (const rustEditor of vscode.window.visibleTextEditors.filter( | ||
90 | editor => this.isRustDocument(editor.document), | ||
91 | )) { | ||
92 | if (newDecorations !== undefined) { | ||
93 | promises.push( | ||
94 | Promise.resolve( | ||
95 | rustEditor.setDecorations( | ||
96 | typeHintDecorationType, | ||
97 | newDecorations, | ||
98 | ), | ||
99 | ), | ||
100 | ); | ||
101 | } else { | ||
102 | promises.push(this.updateDecorationsFromServer(rustEditor)); | ||
103 | } | ||
104 | } | ||
105 | 68 | ||
106 | for (const promise of promises) { | 69 | private async refreshEditor(editor: vscode.TextEditor): Promise<void> { |
107 | await promise; | 70 | const newHints = await this.queryHints(editor.document.uri.toString()); |
108 | } | 71 | if (newHints == null) return; |
72 | const newDecorations = newHints.map(hint => ({ | ||
73 | range: hint.range, | ||
74 | renderOptions: { | ||
75 | after: { | ||
76 | contentText: `: ${hint.label}`, | ||
77 | }, | ||
78 | }, | ||
79 | })); | ||
80 | this.setDecorations(editor, newDecorations); | ||
109 | } | 81 | } |
110 | 82 | ||
111 | private isRustDocument(document: vscode.TextDocument): boolean { | 83 | private get allEditors(): vscode.TextEditor[] { |
112 | return document && document.languageId === 'rust'; | 84 | return vscode.window.visibleTextEditors.filter( |
85 | editor => editor.document.languageId === 'rust', | ||
86 | ); | ||
113 | } | 87 | } |
114 | 88 | ||
115 | private async updateDecorationsFromServer( | 89 | private setDecorations( |
116 | editor: vscode.TextEditor, | 90 | editor: vscode.TextEditor, |
117 | ): Promise<void> { | 91 | decorations: vscode.DecorationOptions[], |
118 | const newHints = await this.queryHints(editor.document.uri.toString()); | 92 | ) { |
119 | if (newHints !== null) { | 93 | editor.setDecorations( |
120 | const newDecorations = newHints.map(hint => ({ | 94 | typeHintDecorationType, |
121 | range: hint.range, | 95 | this.enabled ? decorations : [], |
122 | renderOptions: { | 96 | ); |
123 | after: { | ||
124 | contentText: `: ${hint.label}`, | ||
125 | }, | ||
126 | }, | ||
127 | })); | ||
128 | return editor.setDecorations( | ||
129 | typeHintDecorationType, | ||
130 | newDecorations, | ||
131 | ); | ||
132 | } | ||
133 | } | 97 | } |
134 | 98 | ||
135 | private async queryHints(documentUri: string): Promise<InlayHint[] | null> { | 99 | private async queryHints(documentUri: string): Promise<InlayHint[] | null> { |
136 | const request: InlayHintsParams = { | 100 | const request: InlayHintsParams = { |
137 | textDocument: { uri: documentUri }, | 101 | textDocument: { uri: documentUri }, |
138 | }; | 102 | }; |
139 | const client = Server.client; | 103 | let tokenSource = new vscode.CancellationTokenSource(); |
140 | return client | 104 | let prev = this.pending.get(documentUri); |
141 | .onReady() | 105 | if (prev) prev.cancel() |
142 | .then(() => | 106 | this.pending.set(documentUri, tokenSource); |
143 | client.sendRequest<InlayHint[] | null>( | 107 | try { |
144 | 'rust-analyzer/inlayHints', | 108 | return await this.ctx.sendRequestWithRetry<InlayHint[] | null>( |
145 | request, | 109 | 'rust-analyzer/inlayHints', |
146 | ), | 110 | request, |
111 | tokenSource.token, | ||
147 | ); | 112 | ); |
113 | } finally { | ||
114 | if (!tokenSource.token.isCancellationRequested) { | ||
115 | this.pending.delete(documentUri) | ||
116 | } | ||
117 | } | ||
148 | } | 118 | } |
149 | } | 119 | } |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 7e63a9cac..345ae0685 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -4,10 +4,10 @@ import * as lc from 'vscode-languageclient'; | |||
4 | import * as commands from './commands'; | 4 | import * as commands from './commands'; |
5 | import { activateInlayHints } from './inlay_hints'; | 5 | import { activateInlayHints } from './inlay_hints'; |
6 | import { StatusDisplay } from './status_display'; | 6 | import { StatusDisplay } from './status_display'; |
7 | import * as events from './events'; | ||
8 | import * as notifications from './notifications'; | 7 | import * as notifications from './notifications'; |
9 | import { Server } from './server'; | 8 | import { Server } from './server'; |
10 | import { Ctx } from './ctx'; | 9 | import { Ctx } from './ctx'; |
10 | import { activateHighlighting } from './highlighting'; | ||
11 | 11 | ||
12 | let ctx!: Ctx; | 12 | let ctx!: Ctx; |
13 | 13 | ||
@@ -28,15 +28,15 @@ export async function activate(context: vscode.ExtensionContext) { | |||
28 | ctx.registerCommand('runSingle', commands.runSingle); | 28 | ctx.registerCommand('runSingle', commands.runSingle); |
29 | ctx.registerCommand('showReferences', commands.showReferences); | 29 | ctx.registerCommand('showReferences', commands.showReferences); |
30 | 30 | ||
31 | if (Server.config.enableEnhancedTyping) { | 31 | if (ctx.config.enableEnhancedTyping) { |
32 | ctx.overrideCommand('type', commands.onEnter); | 32 | ctx.overrideCommand('type', commands.onEnter); |
33 | } | 33 | } |
34 | 34 | ||
35 | const watchStatus = new StatusDisplay( | 35 | const watchStatus = new StatusDisplay(ctx.config.cargoWatchOptions.command); |
36 | Server.config.cargoWatchOptions.command, | ||
37 | ); | ||
38 | ctx.pushCleanup(watchStatus); | 36 | ctx.pushCleanup(watchStatus); |
39 | 37 | ||
38 | activateHighlighting(ctx); | ||
39 | |||
40 | // Notifications are events triggered by the language server | 40 | // Notifications are events triggered by the language server |
41 | const allNotifications: [string, lc.GenericNotificationHandler][] = [ | 41 | const allNotifications: [string, lc.GenericNotificationHandler][] = [ |
42 | [ | 42 | [ |
@@ -49,11 +49,6 @@ export async function activate(context: vscode.ExtensionContext) { | |||
49 | ], | 49 | ], |
50 | ]; | 50 | ]; |
51 | 51 | ||
52 | // The events below are plain old javascript events, triggered and handled by vscode | ||
53 | vscode.window.onDidChangeActiveTextEditor( | ||
54 | events.changeActiveTextEditor.makeHandler(), | ||
55 | ); | ||
56 | |||
57 | const startServer = () => Server.start(allNotifications); | 52 | const startServer = () => Server.start(allNotifications); |
58 | const reloadCommand = () => reloadServer(startServer); | 53 | const reloadCommand = () => reloadServer(startServer); |
59 | 54 | ||
@@ -66,7 +61,7 @@ export async function activate(context: vscode.ExtensionContext) { | |||
66 | vscode.window.showErrorMessage(e.message); | 61 | vscode.window.showErrorMessage(e.message); |
67 | } | 62 | } |
68 | 63 | ||
69 | if (Server.config.displayInlayHints) { | 64 | if (ctx.config.displayInlayHints) { |
70 | activateInlayHints(ctx); | 65 | activateInlayHints(ctx); |
71 | } | 66 | } |
72 | } | 67 | } |