aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-12-30 21:57:08 +0000
committerGitHub <[email protected]>2019-12-30 21:57:08 +0000
commitc3d74744cdae29aa6a6bfa0cd7ab64b8b251e287 (patch)
treebf53aac74110d30eba6da346e764e003ffd8b9c4
parent17dda0972a68dd88a766c223390317dc2cb3ea00 (diff)
parentcdd7118cbf23e21c376092b3b2734407004b8dbf (diff)
Merge #2694
2694: Refactor inlay hints r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs15
-rw-r--r--editors/code/rollup.config.js2
-rw-r--r--editors/code/src/ctx.ts23
-rw-r--r--editors/code/src/events/change_active_text_editor.ts25
-rw-r--r--editors/code/src/events/index.ts3
-rw-r--r--editors/code/src/highlighting.ts23
-rw-r--r--editors/code/src/inlay_hints.ts176
-rw-r--r--editors/code/src/main.ts17
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 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import { Server } from './server'; 3import { Server } from './server';
4import { Config } from './config';
4 5
5export class Ctx { 6export 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
61export type Cmd = (...args: any[]) => any; 82export type Cmd = (...args: any[]) => any;
83
84const 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 @@
1import { TextEditor } from 'vscode';
2import { TextDocumentIdentifier } from 'vscode-languageclient';
3import { Decoration } from '../highlighting';
4import { Server } from '../server';
5
6export 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 @@
1import * as changeActiveTextEditor from './change_active_text_editor';
2
3export { 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 @@
1import seedrandom = require('seedrandom');
2import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
3import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import * as seedrandom_ from 'seedrandom';
4const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular2-library/issues/221#issuecomment-355945207
5
4import * as scopes from './scopes'; 6import * as scopes from './scopes';
5import * as scopesMapper from './scopes_mapper'; 7import * as scopesMapper from './scopes_mapper';
6 8
7import { Server } from './server'; 9import { Server } from './server';
10import { Ctx } from './ctx';
11
12export 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
9export interface Decoration { 30export 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 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import { Server } from './server'; 3
4import { Ctx } from './ctx'; 4import { Ctx } from './ctx';
5 5
6export function activateInlayHints(ctx: Ctx) { 6export 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
41interface InlayHintsParams { 27interface InlayHintsParams {
@@ -55,95 +41,79 @@ const typeHintDecorationType = vscode.window.createTextEditorDecorationType({
55}); 41});
56 42
57class HintsUpdater { 43class 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';
4import * as commands from './commands'; 4import * as commands from './commands';
5import { activateInlayHints } from './inlay_hints'; 5import { activateInlayHints } from './inlay_hints';
6import { StatusDisplay } from './status_display'; 6import { StatusDisplay } from './status_display';
7import * as events from './events';
8import * as notifications from './notifications'; 7import * as notifications from './notifications';
9import { Server } from './server'; 8import { Server } from './server';
10import { Ctx } from './ctx'; 9import { Ctx } from './ctx';
10import { activateHighlighting } from './highlighting';
11 11
12let ctx!: Ctx; 12let 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}