From 7646dc046eb562276231de8ec6a4df1bc691cafc Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Dec 2019 20:29:21 +0100 Subject: Encapsulate highlighting activation --- .../code/src/events/change_active_text_editor.ts | 25 ---------------------- editors/code/src/events/index.ts | 3 --- editors/code/src/highlighting.ts | 22 ++++++++++++++++++- editors/code/src/main.ts | 9 +++----- 4 files changed, 24 insertions(+), 35 deletions(-) delete mode 100644 editors/code/src/events/change_active_text_editor.ts delete mode 100644 editors/code/src/events/index.ts 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 @@ -import { TextEditor } from 'vscode'; -import { TextDocumentIdentifier } from 'vscode-languageclient'; -import { Decoration } from '../highlighting'; -import { Server } from '../server'; - -export function makeHandler() { - return async function handle(editor: TextEditor | undefined) { - if (!editor || editor.document.languageId !== 'rust') { - return; - } - - if (!Server.config.highlightingOn) { - return; - } - - const params: TextDocumentIdentifier = { - uri: editor.document.uri.toString(), - }; - const decorations = await Server.client.sendRequest( - 'rust-analyzer/decorationsRequest', - params, - ); - Server.highlighter.setHighlights(editor, decorations); - }; -} 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 @@ -import * as changeActiveTextEditor from './change_active_text_editor'; - -export { changeActiveTextEditor }; diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index 4e224a54c..ced78adc1 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -1,10 +1,30 @@ -import seedrandom = require('seedrandom'); import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; +import * as seedrandom from 'seedrandom'; + import * as scopes from './scopes'; import * as scopesMapper from './scopes_mapper'; import { Server } from './server'; +import { Ctx } from './ctx'; + +export function activateHighlighting(ctx: Ctx) { + vscode.window.onDidChangeActiveTextEditor( + async (editor: vscode.TextEditor | undefined) => { + if (!editor || editor.document.languageId !== 'rust') return; + if (!Server.config.highlightingOn) return; + + const params: lc.TextDocumentIdentifier = { + uri: editor.document.uri.toString(), + }; + const decorations = await ctx.client.sendRequest( + 'rust-analyzer/decorationsRequest', + params, + ); + Server.highlighter.setHighlights(editor, decorations); + }, + ); +} export interface Decoration { range: lc.Range; diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 7e63a9cac..45657532e 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -4,10 +4,10 @@ import * as lc from 'vscode-languageclient'; import * as commands from './commands'; import { activateInlayHints } from './inlay_hints'; import { StatusDisplay } from './status_display'; -import * as events from './events'; import * as notifications from './notifications'; import { Server } from './server'; import { Ctx } from './ctx'; +import { activateHighlighting } from './highlighting'; let ctx!: Ctx; @@ -37,6 +37,8 @@ export async function activate(context: vscode.ExtensionContext) { ); ctx.pushCleanup(watchStatus); + activateHighlighting(ctx); + // Notifications are events triggered by the language server const allNotifications: [string, lc.GenericNotificationHandler][] = [ [ @@ -49,11 +51,6 @@ export async function activate(context: vscode.ExtensionContext) { ], ]; - // The events below are plain old javascript events, triggered and handled by vscode - vscode.window.onDidChangeActiveTextEditor( - events.changeActiveTextEditor.makeHandler(), - ); - const startServer = () => Server.start(allNotifications); const reloadCommand = () => reloadServer(startServer); -- cgit v1.2.3 From efbbc903e68aaf32ee1fba5537769070cd2d01e8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Dec 2019 20:46:14 +0100 Subject: Add config to Ctx --- editors/code/src/ctx.ts | 5 +++++ editors/code/src/highlighting.ts | 2 +- editors/code/src/inlay_hints.ts | 4 +--- editors/code/src/main.ts | 8 +++----- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index c3a3583b5..ca4319064 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import { Server } from './server'; +import { Config } from './config'; export class Ctx { private extCtx: vscode.ExtensionContext; @@ -13,6 +14,10 @@ export class Ctx { return Server.client; } + get config(): Config { + return Server.config; + } + get activeRustEditor(): vscode.TextEditor | undefined { const editor = vscode.window.activeTextEditor; return editor && editor.document.languageId === 'rust' diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index ced78adc1..0f9271de2 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -12,7 +12,7 @@ export function activateHighlighting(ctx: Ctx) { vscode.window.onDidChangeActiveTextEditor( async (editor: vscode.TextEditor | undefined) => { if (!editor || editor.document.languageId !== 'rust') return; - if (!Server.config.highlightingOn) return; + if (!ctx.config.highlightingOn) return; const params: lc.TextDocumentIdentifier = { uri: editor.document.uri.toString(), diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 4581e2278..fb8f135c3 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts @@ -30,9 +30,7 @@ export function activateInlayHints(ctx: Ctx) { ); ctx.pushCleanup( vscode.workspace.onDidChangeConfiguration(_ => - hintsUpdater.toggleHintsDisplay( - Server.config.displayInlayHints, - ), + hintsUpdater.toggleHintsDisplay(ctx.config.displayInlayHints), ), ); }); diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 45657532e..345ae0685 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -28,13 +28,11 @@ export async function activate(context: vscode.ExtensionContext) { ctx.registerCommand('runSingle', commands.runSingle); ctx.registerCommand('showReferences', commands.showReferences); - if (Server.config.enableEnhancedTyping) { + if (ctx.config.enableEnhancedTyping) { ctx.overrideCommand('type', commands.onEnter); } - const watchStatus = new StatusDisplay( - Server.config.cargoWatchOptions.command, - ); + const watchStatus = new StatusDisplay(ctx.config.cargoWatchOptions.command); ctx.pushCleanup(watchStatus); activateHighlighting(ctx); @@ -63,7 +61,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.window.showErrorMessage(e.message); } - if (Server.config.displayInlayHints) { + if (ctx.config.displayInlayHints) { activateInlayHints(ctx); } } -- cgit v1.2.3 From ac8a142dddc697d26d5aa8c878df933300163e09 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Dec 2019 21:28:38 +0100 Subject: Refactor inlay hints --- editors/code/src/inlay_hints.ts | 170 ++++++++++++++++------------------------ 1 file changed, 67 insertions(+), 103 deletions(-) diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index fb8f135c3..aae9de69c 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts @@ -1,39 +1,29 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; -import { Server } from './server'; + import { Ctx } from './ctx'; export function activateInlayHints(ctx: Ctx) { - const hintsUpdater = new HintsUpdater(); - hintsUpdater.refreshHintsForVisibleEditors().then(() => { - // vscode may ignore top level hintsUpdater.refreshHintsForVisibleEditors() - // so update the hints once when the focus changes to guarantee their presence - let editorChangeDisposable: vscode.Disposable | null = null; - editorChangeDisposable = vscode.window.onDidChangeActiveTextEditor( - _ => { - if (editorChangeDisposable !== null) { - editorChangeDisposable.dispose(); - } - return hintsUpdater.refreshHintsForVisibleEditors(); - }, - ); + const hintsUpdater = new HintsUpdater(ctx); + console.log('activateInlayHints'); - ctx.pushCleanup( - vscode.window.onDidChangeVisibleTextEditors(_ => - hintsUpdater.refreshHintsForVisibleEditors(), - ), - ); - ctx.pushCleanup( - vscode.workspace.onDidChangeTextDocument(e => - hintsUpdater.refreshHintsForVisibleEditors(e), - ), - ); - ctx.pushCleanup( - vscode.workspace.onDidChangeConfiguration(_ => - hintsUpdater.toggleHintsDisplay(ctx.config.displayInlayHints), - ), - ); - }); + vscode.window.onDidChangeVisibleTextEditors(async _ => { + await hintsUpdater.refresh(); + }, ctx.subscriptions); + + vscode.workspace.onDidChangeTextDocument(async e => { + if (e.contentChanges.length === 0) return; + if (e.document.languageId !== 'rust') return; + await hintsUpdater.refresh(); + }, ctx.subscriptions); + + vscode.workspace.onDidChangeConfiguration(_ => { + hintsUpdater.setEnabled(ctx.config.displayInlayHints); + }, ctx.subscriptions); + + // XXX: don't await here; + // Who knows what happens if an exception is thrown here... + hintsUpdater.refresh(); } interface InlayHintsParams { @@ -53,95 +43,69 @@ const typeHintDecorationType = vscode.window.createTextEditorDecorationType({ }); class HintsUpdater { - private displayHints = true; - - public async toggleHintsDisplay(displayHints: boolean): Promise { - if (this.displayHints !== displayHints) { - this.displayHints = displayHints; - return this.refreshVisibleEditorsHints( - displayHints ? undefined : [], - ); - } - } + private ctx: Ctx; + private enabled = true; - public async refreshHintsForVisibleEditors( - cause?: vscode.TextDocumentChangeEvent, - ): Promise { - if (!this.displayHints) return; - - if ( - cause !== undefined && - (cause.contentChanges.length === 0 || - !this.isRustDocument(cause.document)) - ) { - return; - } - return this.refreshVisibleEditorsHints(); + constructor(ctx: Ctx) { + this.ctx = ctx; } - private async refreshVisibleEditorsHints( - newDecorations?: vscode.DecorationOptions[], - ) { - const promises: Array> = []; - - for (const rustEditor of vscode.window.visibleTextEditors.filter( - editor => this.isRustDocument(editor.document), - )) { - if (newDecorations !== undefined) { - promises.push( - Promise.resolve( - rustEditor.setDecorations( - typeHintDecorationType, - newDecorations, - ), - ), - ); - } else { - promises.push(this.updateDecorationsFromServer(rustEditor)); - } - } + async setEnabled(enabled: boolean) { + if (this.enabled == enabled) return; + this.enabled = enabled; - for (const promise of promises) { - await promise; + if (this.enabled) { + await this.refresh(); + } else { + this.allEditors.forEach(it => this.setDecorations(it, [])); } } - private isRustDocument(document: vscode.TextDocument): boolean { - return document && document.languageId === 'rust'; + async refresh() { + if (!this.enabled) return; + const promises = this.allEditors.map(it => this.refreshEditor(it)); + await Promise.all(promises); } - private async updateDecorationsFromServer( - editor: vscode.TextEditor, - ): Promise { + private async refreshEditor(editor: vscode.TextEditor): Promise { const newHints = await this.queryHints(editor.document.uri.toString()); - if (newHints !== null) { - const newDecorations = newHints.map(hint => ({ - range: hint.range, - renderOptions: { - after: { - contentText: `: ${hint.label}`, - }, + + const newDecorations = (newHints ? newHints : []).map(hint => ({ + range: hint.range, + renderOptions: { + after: { + contentText: `: ${hint.label}`, }, - })); - return editor.setDecorations( - typeHintDecorationType, - newDecorations, - ); - } + }, + })); + this.setDecorations(editor, newDecorations); + } + + private get allEditors(): vscode.TextEditor[] { + return vscode.window.visibleTextEditors.filter( + editor => editor.document.languageId === 'rust', + ); + } + + private setDecorations( + editor: vscode.TextEditor, + decorations: vscode.DecorationOptions[], + ) { + editor.setDecorations( + typeHintDecorationType, + this.enabled ? decorations : [], + ); } private async queryHints(documentUri: string): Promise { const request: InlayHintsParams = { textDocument: { uri: documentUri }, }; - const client = Server.client; - return client - .onReady() - .then(() => - client.sendRequest( - 'rust-analyzer/inlayHints', - request, - ), - ); + await this.ctx.client.onReady(); + + return this.ctx.client.sendRequest( + 'rust-analyzer/inlayHints', + request, + ); } } -- cgit v1.2.3 From 08c5d157f9a24f0094c90c556019a20bf85e5a07 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Dec 2019 21:39:34 +0100 Subject: Thoughtlessly copy-paste a fix to a problem I don't understand --- editors/code/src/highlighting.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index 0f9271de2..333319b85 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; -import * as seedrandom from 'seedrandom'; +import * as seedrandom_ from 'seedrandom'; +const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular2-library/issues/221#issuecomment-355945207 import * as scopes from './scopes'; import * as scopesMapper from './scopes_mapper'; -- cgit v1.2.3 From 23bac120625ca96402426e241c91ed5f3d7ccc02 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Dec 2019 22:18:16 +0100 Subject: Retry inlay hints on content modified error --- crates/ra_lsp_server/src/main_loop.rs | 15 +++++---------- editors/code/rollup.config.js | 2 +- editors/code/src/ctx.ts | 15 +++++++++++++++ editors/code/src/inlay_hints.ts | 7 +------ 4 files changed, 22 insertions(+), 17 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 Ok(lsp_error) => Response::new_err(id, lsp_error.code, lsp_error.message), Err(e) => { if is_canceled(&e) { - // FIXME: When https://github.com/Microsoft/vscode-languageserver-node/issues/457 - // gets fixed, we can return the proper response. - // This works around the issue where "content modified" error would continuously - // show an message pop-up in VsCode - // Response::err( - // id, - // ErrorCode::ContentModified as i32, - // "content modified".to_string(), - // ) - Response::new_ok(id, ()) + Response::new_err( + id, + ErrorCode::ContentModified as i32, + "content modified".to_string(), + ) } else { Response::new_err(id, ErrorCode::InternalError as i32, e.to_string()) } 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 { commonjs({ namedExports: { // squelch missing import warnings - 'vscode-languageclient': ['CreateFile', 'RenameFile'] + 'vscode-languageclient': ['CreateFile', 'RenameFile', 'ErrorCodes'] } }) ], diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index ca4319064..75b3542f4 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -61,6 +61,21 @@ export class Ctx { pushCleanup(d: { dispose(): any }) { this.extCtx.subscriptions.push(d); } + + async sendRequestWithRetry(method: string, param: any): Promise { + await this.client.onReady(); + const nRetries = 3; + for (let triesLeft = nRetries; ; triesLeft--) { + try { + return await this.client.sendRequest(method, param); + } catch (e) { + if (e.code === lc.ErrorCodes.ContentModified && triesLeft > 0) { + continue; + } + throw e; + } + } + } } export type Cmd = (...args: any[]) => any; diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index aae9de69c..16faea22e 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts @@ -5,8 +5,6 @@ import { Ctx } from './ctx'; export function activateInlayHints(ctx: Ctx) { const hintsUpdater = new HintsUpdater(ctx); - console.log('activateInlayHints'); - vscode.window.onDidChangeVisibleTextEditors(async _ => { await hintsUpdater.refresh(); }, ctx.subscriptions); @@ -69,7 +67,6 @@ class HintsUpdater { private async refreshEditor(editor: vscode.TextEditor): Promise { const newHints = await this.queryHints(editor.document.uri.toString()); - const newDecorations = (newHints ? newHints : []).map(hint => ({ range: hint.range, renderOptions: { @@ -101,9 +98,7 @@ class HintsUpdater { const request: InlayHintsParams = { textDocument: { uri: documentUri }, }; - await this.ctx.client.onReady(); - - return this.ctx.client.sendRequest( + return this.ctx.sendRequestWithRetry( 'rust-analyzer/inlayHints', request, ); -- cgit v1.2.3 From cdd7118cbf23e21c376092b3b2734407004b8dbf Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Dec 2019 22:53:21 +0100 Subject: Don't request inline hints repeatedly --- editors/code/src/ctx.ts | 13 ++++++++----- editors/code/src/inlay_hints.ts | 23 ++++++++++++++++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 75b3542f4..d3ef27e43 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -62,20 +62,23 @@ export class Ctx { this.extCtx.subscriptions.push(d); } - async sendRequestWithRetry(method: string, param: any): Promise { + async sendRequestWithRetry(method: string, param: any, token: vscode.CancellationToken): Promise { await this.client.onReady(); - const nRetries = 3; - for (let triesLeft = nRetries; ; triesLeft--) { + for (const delay of [2, 4, 6, 8, 10, null]) { try { - return await this.client.sendRequest(method, param); + return await this.client.sendRequest(method, param, token); } catch (e) { - if (e.code === lc.ErrorCodes.ContentModified && triesLeft > 0) { + if (e.code === lc.ErrorCodes.ContentModified && delay !== null) { + await sleep(10 * (1 << delay)) continue; } throw e; } } + throw 'unreachable' } } export type Cmd = (...args: any[]) => any; + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 16faea22e..d41297407 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts @@ -41,6 +41,7 @@ const typeHintDecorationType = vscode.window.createTextEditorDecorationType({ }); class HintsUpdater { + private pending: Map = new Map(); private ctx: Ctx; private enabled = true; @@ -67,7 +68,8 @@ class HintsUpdater { private async refreshEditor(editor: vscode.TextEditor): Promise { const newHints = await this.queryHints(editor.document.uri.toString()); - const newDecorations = (newHints ? newHints : []).map(hint => ({ + if (newHints == null) return; + const newDecorations = newHints.map(hint => ({ range: hint.range, renderOptions: { after: { @@ -98,9 +100,20 @@ class HintsUpdater { const request: InlayHintsParams = { textDocument: { uri: documentUri }, }; - return this.ctx.sendRequestWithRetry( - 'rust-analyzer/inlayHints', - request, - ); + let tokenSource = new vscode.CancellationTokenSource(); + let prev = this.pending.get(documentUri); + if (prev) prev.cancel() + this.pending.set(documentUri, tokenSource); + try { + return await this.ctx.sendRequestWithRetry( + 'rust-analyzer/inlayHints', + request, + tokenSource.token, + ); + } finally { + if (!tokenSource.token.isCancellationRequested) { + this.pending.delete(documentUri) + } + } } } -- cgit v1.2.3