From e4b588868f822b9c200a8ce77d24bfab5aeca4b8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 31 Dec 2019 17:22:43 +0100 Subject: Refactor status activation --- editors/code/src/ctx.ts | 5 +++++ editors/code/src/highlighting.ts | 42 ++++++++++++++++++-------------------- editors/code/src/main.ts | 17 +++------------ editors/code/src/server.ts | 9 +------- editors/code/src/status_display.ts | 10 ++++++++- 5 files changed, 38 insertions(+), 45 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 693ce05ed..0e62a3a85 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -84,6 +84,11 @@ export class Ctx { } throw 'unreachable'; } + + onNotification(method: string, handler: lc.GenericNotificationHandler) { + this.client.onReady() + .then(() => this.client.onNotification(method, handler)) + } } export type Cmd = (...args: any[]) => any; diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index d383d87ef..d4e961b5b 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -10,28 +10,26 @@ import { Ctx } from './ctx'; export function activateHighlighting(ctx: Ctx) { const highlighter = new Highlighter(ctx); - ctx.client.onReady().then(() => { - ctx.client.onNotification( - 'rust-analyzer/publishDecorations', - (params: PublishDecorationsParams) => { - if (!ctx.config.highlightingOn) return; - - const targetEditor = vscode.window.visibleTextEditors.find( - editor => { - const unescapedUri = unescape( - editor.document.uri.toString(), - ); - // Unescaped URI looks like: - // file:///c:/Workspace/ra-test/src/main.rs - return unescapedUri === params.uri; - }, - ); - if (!targetEditor) return; - - highlighter.setHighlights(targetEditor, params.decorations); - }, - ); - }); + ctx.onNotification( + 'rust-analyzer/publishDecorations', + (params: PublishDecorationsParams) => { + if (!ctx.config.highlightingOn) return; + + const targetEditor = vscode.window.visibleTextEditors.find( + editor => { + const unescapedUri = unescape( + editor.document.uri.toString(), + ); + // Unescaped URI looks like: + // file:///c:/Workspace/ra-test/src/main.rs + return unescapedUri === params.uri; + }, + ); + if (!targetEditor) return; + + highlighter.setHighlights(targetEditor, params.decorations); + }, + ); vscode.workspace.onDidChangeConfiguration( _ => highlighter.removeHighlights(), diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 0c4abdac8..511f17ca4 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -1,9 +1,8 @@ import * as vscode from 'vscode'; -import * as lc from 'vscode-languageclient'; import * as commands from './commands'; import { activateInlayHints } from './inlay_hints'; -import { StatusDisplay } from './status_display'; +import { activateStatusDisplay } from './status_display'; import { Server } from './server'; import { Ctx } from './ctx'; import { activateHighlighting } from './highlighting'; @@ -32,18 +31,7 @@ export async function activate(context: vscode.ExtensionContext) { ctx.overrideCommand('type', commands.onEnter); } - const watchStatus = new StatusDisplay(ctx.config.cargoWatchOptions.command); - ctx.pushCleanup(watchStatus); - - // Notifications are events triggered by the language server - const allNotifications: [string, lc.GenericNotificationHandler][] = [ - [ - '$/progress', - params => watchStatus.handleProgressNotification(params), - ], - ]; - - const startServer = () => Server.start(allNotifications); + const startServer = () => Server.start(); const reloadCommand = () => reloadServer(startServer); vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); @@ -55,6 +43,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.window.showErrorMessage(e.message); } + activateStatusDisplay(ctx); activateHighlighting(ctx); if (ctx.config.displayInlayHints) { diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts index 2bb21da6b..5dc8a36bd 100644 --- a/editors/code/src/server.ts +++ b/editors/code/src/server.ts @@ -15,9 +15,7 @@ export class Server { public static config = new Config(); public static client: lc.LanguageClient; - public static async start( - notificationHandlers: Iterable<[string, lc.GenericNotificationHandler]>, - ) { + public static async start() { // '.' Is the fallback if no folder is open // 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. let folder: string = '.'; @@ -92,11 +90,6 @@ export class Server { }, }; Server.client.registerProposedFeatures(); - Server.client.onReady().then(() => { - for (const [type, handler] of notificationHandlers) { - Server.client.onNotification(type, handler); - } - }); Server.client.start(); } } diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts index 48cf0655b..e3719075b 100644 --- a/editors/code/src/status_display.ts +++ b/editors/code/src/status_display.ts @@ -1,8 +1,16 @@ import * as vscode from 'vscode'; +import { Ctx } from './ctx'; + const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; -export class StatusDisplay implements vscode.Disposable { +export function activateStatusDisplay(ctx: Ctx) { + const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); + ctx.pushCleanup(statusDisplay); + ctx.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params)); +} + +class StatusDisplay implements vscode.Disposable { packageName?: string; private i = 0; -- cgit v1.2.3 From 433000be34eafd052addd91afd605a81e137a433 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 31 Dec 2019 17:28:01 +0100 Subject: Move config to Ctx --- editors/code/src/ctx.ts | 5 +---- editors/code/src/main.ts | 2 +- editors/code/src/server.ts | 5 +++-- 3 files changed, 5 insertions(+), 7 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 0e62a3a85..d86fe5a87 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -4,6 +4,7 @@ import { Server } from './server'; import { Config } from './config'; export class Ctx { + readonly config = new Config(); private extCtx: vscode.ExtensionContext; constructor(extCtx: vscode.ExtensionContext) { @@ -14,10 +15,6 @@ 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/main.ts b/editors/code/src/main.ts index 511f17ca4..3d9107927 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -31,7 +31,7 @@ export async function activate(context: vscode.ExtensionContext) { ctx.overrideCommand('type', commands.onEnter); } - const startServer = () => Server.start(); + const startServer = () => Server.start(ctx.config); const reloadCommand = () => reloadServer(startServer); vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts index 5dc8a36bd..ab9f3bfa6 100644 --- a/editors/code/src/server.ts +++ b/editors/code/src/server.ts @@ -12,10 +12,10 @@ function expandPathResolving(path: string) { } export class Server { - public static config = new Config(); + static config: Config; public static client: lc.LanguageClient; - public static async start() { + public static async start(config: Config) { // '.' Is the fallback if no folder is open // 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. let folder: string = '.'; @@ -23,6 +23,7 @@ export class Server { folder = workspace.workspaceFolders[0].uri.fsPath.toString(); } + this.config = config; const command = expandPathResolving(this.config.raLspServerPath); const run: lc.Executable = { command, -- cgit v1.2.3 From 76f283108b5fc7aeb105eee0e5d44cae2ffab173 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 31 Dec 2019 17:28:27 +0100 Subject: Drop needless pubs --- editors/code/src/config.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index ccb0ee2b7..9800b461f 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -16,25 +16,25 @@ export interface CargoFeatures { } export class Config { - public highlightingOn = true; - public rainbowHighlightingOn = false; - public enableEnhancedTyping = true; - public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; - public lruCapacity: null | number = null; - public displayInlayHints = true; - public maxInlayHintLength: null | number = null; - public excludeGlobs = []; - public useClientWatching = true; - public featureFlags = {}; + highlightingOn = true; + rainbowHighlightingOn = false; + enableEnhancedTyping = true; + raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; + lruCapacity: null | number = null; + displayInlayHints = true; + maxInlayHintLength: null | number = null; + excludeGlobs = []; + useClientWatching = true; + featureFlags = {}; // for internal use - public withSysroot: null | boolean = null; - public cargoWatchOptions: CargoWatchOptions = { + withSysroot: null | boolean = null; + cargoWatchOptions: CargoWatchOptions = { enable: true, arguments: [], command: '', allTargets: true, }; - public cargoFeatures: CargoFeatures = { + cargoFeatures: CargoFeatures = { noDefaultFeatures: false, allFeatures: true, features: [], @@ -50,7 +50,7 @@ export class Config { this.userConfigChanged(); } - public userConfigChanged() { + userConfigChanged() { const config = vscode.workspace.getConfiguration('rust-analyzer'); let requireReloadMessage = null; -- cgit v1.2.3 From 0849f7001cac6af93ce9e9356f8c21881bbe34c5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 31 Dec 2019 17:34:52 +0100 Subject: Refactor config --- editors/code/src/config.ts | 10 ++++------ editors/code/src/ctx.ts | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 9800b461f..ec2790b63 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -43,14 +43,12 @@ export class Config { private prevEnhancedTyping: null | boolean = null; private prevCargoFeatures: null | CargoFeatures = null; - constructor() { - vscode.workspace.onDidChangeConfiguration(_ => - this.userConfigChanged(), - ); - this.userConfigChanged(); + constructor(ctx: vscode.ExtensionContext) { + vscode.workspace.onDidChangeConfiguration(_ => this.refresh(), ctx.subscriptions); + this.refresh(); } - userConfigChanged() { + private refresh() { const config = vscode.workspace.getConfiguration('rust-analyzer'); let requireReloadMessage = null; diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index d86fe5a87..393d6a602 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -4,10 +4,11 @@ import { Server } from './server'; import { Config } from './config'; export class Ctx { - readonly config = new Config(); + readonly config: Config; private extCtx: vscode.ExtensionContext; constructor(extCtx: vscode.ExtensionContext) { + this.config = new Config(extCtx) this.extCtx = extCtx; } -- cgit v1.2.3 From 087af54069d34eef5197e04d64ac322d9ee98085 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 31 Dec 2019 18:14:00 +0100 Subject: Refactor server lifecycle --- editors/code/src/client.ts | 90 ++++++++++++++++++++++++++ editors/code/src/commands/analyzer_status.ts | 5 +- editors/code/src/commands/expand_macro.ts | 5 +- editors/code/src/commands/index.ts | 25 ++++++-- editors/code/src/commands/join_lines.ts | 7 +- editors/code/src/ctx.ts | 76 +++++++++++++--------- editors/code/src/highlighting.ts | 56 ++++++++-------- editors/code/src/inlay_hints.ts | 11 ++-- editors/code/src/main.ts | 35 ++-------- editors/code/src/server.ts | 96 ---------------------------- editors/code/src/source_change.ts | 9 ++- editors/code/src/status_display.ts | 4 +- 12 files changed, 218 insertions(+), 201 deletions(-) create mode 100644 editors/code/src/client.ts delete mode 100644 editors/code/src/server.ts (limited to 'editors/code') 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 @@ +import { homedir } from 'os'; +import * as lc from 'vscode-languageclient'; + +import { window, workspace } from 'vscode'; +import { Config } from './config'; + +export function createClient(config: Config): lc.LanguageClient { + // '.' Is the fallback if no folder is open + // 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. + let folder: string = '.'; + if (workspace.workspaceFolders !== undefined) { + folder = workspace.workspaceFolders[0].uri.fsPath.toString(); + } + + const command = expandPathResolving(config.raLspServerPath); + const run: lc.Executable = { + command, + options: { cwd: folder }, + }; + const serverOptions: lc.ServerOptions = { + run, + debug: run, + }; + const traceOutputChannel = window.createOutputChannel( + 'Rust Analyzer Language Server Trace', + ); + const clientOptions: lc.LanguageClientOptions = { + documentSelector: [{ scheme: 'file', language: 'rust' }], + initializationOptions: { + publishDecorations: true, + lruCapacity: config.lruCapacity, + maxInlayHintLength: config.maxInlayHintLength, + cargoWatchEnable: config.cargoWatchOptions.enable, + cargoWatchArgs: config.cargoWatchOptions.arguments, + cargoWatchCommand: config.cargoWatchOptions.command, + cargoWatchAllTargets: + config.cargoWatchOptions.allTargets, + excludeGlobs: config.excludeGlobs, + useClientWatching: config.useClientWatching, + featureFlags: config.featureFlags, + withSysroot: config.withSysroot, + cargoFeatures: config.cargoFeatures, + }, + traceOutputChannel, + }; + + const res = new lc.LanguageClient( + 'rust-analyzer', + 'Rust Analyzer Language Server', + serverOptions, + clientOptions, + ); + + // HACK: This is an awful way of filtering out the decorations notifications + // However, pending proper support, this is the most effecitve approach + // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages + // Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting) + // This also requires considering our settings strategy, which is work which needs doing + // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests + res._tracer = { + log: (messageOrDataObject: string | any, data?: string) => { + if (typeof messageOrDataObject === 'string') { + if ( + messageOrDataObject.includes( + 'rust-analyzer/publishDecorations', + ) || + messageOrDataObject.includes( + 'rust-analyzer/decorationsRequest', + ) + ) { + // Don't log publish decorations requests + } else { + // @ts-ignore This is just a utility function + res.logTrace(messageOrDataObject, data); + } + } else { + // @ts-ignore + res.logObjectTrace(messageOrDataObject); + } + }, + }; + res.registerProposedFeatures() + return res; +} +function expandPathResolving(path: string) { + if (path.startsWith('~/')) { + return path.replace('~', homedir()); + } + return path; +} 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 _uri: vscode.Uri, ): vscode.ProviderResult { const editor = vscode.window.activeTextEditor; - if (editor == null) return ''; + const client = this.ctx.client + if (!editor || !client) return ''; - return this.ctx.client.sendRequest( + return client.sendRequest( 'rust-analyzer/analyzerStatus', null, ); 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 async provideTextDocumentContent(_uri: vscode.Uri): Promise { const editor = vscode.window.activeTextEditor; - if (editor == null) return ''; + const client = this.ctx.client + if (!editor || !client) return ''; const position = editor.selection.active; const request: lc.TextDocumentPositionParams = { textDocument: { uri: editor.document.uri.toString() }, position, }; - const expanded = await this.ctx.client.sendRequest( + const expanded = await client.sendRequest( 'rust-analyzer/expandMacro', request, ); 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'; function collectGarbage(ctx: Ctx): Cmd { return async () => { - ctx.client.sendRequest('rust-analyzer/collectGarbage', null); + ctx.client?.sendRequest('rust-analyzer/collectGarbage', null); }; } function showReferences(ctx: Ctx): Cmd { return (uri: string, position: lc.Position, locations: lc.Location[]) => { - vscode.commands.executeCommand( - 'editor.action.showReferences', - vscode.Uri.parse(uri), - ctx.client.protocol2CodeConverter.asPosition(position), - locations.map(ctx.client.protocol2CodeConverter.asLocation), - ); + let client = ctx.client; + if (client) { + vscode.commands.executeCommand( + 'editor.action.showReferences', + vscode.Uri.parse(uri), + client.protocol2CodeConverter.asPosition(position), + locations.map(client.protocol2CodeConverter.asLocation), + ); + } }; } @@ -36,6 +39,13 @@ function applySourceChange(ctx: Ctx): Cmd { } } +function reload(ctx: Ctx): Cmd { + return async () => { + vscode.window.showInformationMessage('Reloading rust-analyzer...'); + await ctx.restartServer(); + } +} + export { analyzerStatus, expandMacro, @@ -49,4 +59,5 @@ export { runSingle, showReferences, applySourceChange, + reload }; 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'; export function joinLines(ctx: Ctx): Cmd { return async () => { const editor = ctx.activeRustEditor; - if (!editor) return; + const client = ctx.client; + if (!editor || !client) return; const request: JoinLinesParams = { - range: ctx.client.code2ProtocolConverter.asRange(editor.selection), + range: client.code2ProtocolConverter.asRange(editor.selection), textDocument: { uri: editor.document.uri.toString() }, }; - const change = await ctx.client.sendRequest( + const change = await client.sendRequest( 'rust-analyzer/joinLines', request, ); diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 393d6a602..13988056a 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -1,19 +1,38 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; -import { Server } from './server'; import { Config } from './config'; +import { createClient } from './client' export class Ctx { readonly config: Config; + // Because we have "reload server" action, various listeners **will** face a + // situation where the client is not ready yet, and should be prepared to + // deal with it. + // + // Ideally, this should be replaced with async getter though. + client: lc.LanguageClient | null = null private extCtx: vscode.ExtensionContext; + private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = []; constructor(extCtx: vscode.ExtensionContext) { this.config = new Config(extCtx) this.extCtx = extCtx; } - get client(): lc.LanguageClient { - return Server.client; + async restartServer() { + let old = this.client; + if (old) { + await old.stop() + } + this.client = null; + const client = createClient(this.config); + this.pushCleanup(client.start()); + await client.onReady(); + + this.client = client + for (const hook of this.onDidRestartHooks) { + hook(client) + } } get activeRustEditor(): vscode.TextEditor | undefined { @@ -60,35 +79,34 @@ export class Ctx { this.extCtx.subscriptions.push(d); } - async sendRequestWithRetry( - method: string, - param: any, - token?: vscode.CancellationToken, - ): Promise { - await this.client.onReady(); - for (const delay of [2, 4, 6, 8, 10, null]) { - try { - return await (token ? this.client.sendRequest(method, param, token) : this.client.sendRequest(method, param)); - } catch (e) { - if ( - e.code === lc.ErrorCodes.ContentModified && - delay !== null - ) { - await sleep(10 * (1 << delay)); - continue; - } - throw e; - } - } - throw 'unreachable'; - } - - onNotification(method: string, handler: lc.GenericNotificationHandler) { - this.client.onReady() - .then(() => this.client.onNotification(method, handler)) + onDidRestart(hook: (client: lc.LanguageClient) => void) { + this.onDidRestartHooks.push(hook) } } export type Cmd = (...args: any[]) => any; +export async function sendRequestWithRetry( + client: lc.LanguageClient, + method: string, + param: any, + token?: vscode.CancellationToken, +): Promise { + for (const delay of [2, 4, 6, 8, 10, null]) { + try { + return await (token ? client.sendRequest(method, param, token) : client.sendRequest(method, param)); + } catch (e) { + if ( + e.code === lc.ErrorCodes.ContentModified && + delay !== null + ) { + await sleep(10 * (1 << delay)); + continue; + } + throw e; + } + } + throw 'unreachable'; +} + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index d4e961b5b..f9d2e9d90 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -5,31 +5,32 @@ const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular import { ColorTheme, TextMateRuleSettings } from './color_theme'; -import { Ctx } from './ctx'; +import { Ctx, sendRequestWithRetry } from './ctx'; export function activateHighlighting(ctx: Ctx) { const highlighter = new Highlighter(ctx); - - ctx.onNotification( - 'rust-analyzer/publishDecorations', - (params: PublishDecorationsParams) => { - if (!ctx.config.highlightingOn) return; - - const targetEditor = vscode.window.visibleTextEditors.find( - editor => { - const unescapedUri = unescape( - editor.document.uri.toString(), - ); - // Unescaped URI looks like: - // file:///c:/Workspace/ra-test/src/main.rs - return unescapedUri === params.uri; - }, - ); - if (!targetEditor) return; - - highlighter.setHighlights(targetEditor, params.decorations); - }, - ); + ctx.onDidRestart(client => { + client.onNotification( + 'rust-analyzer/publishDecorations', + (params: PublishDecorationsParams) => { + if (!ctx.config.highlightingOn) return; + + const targetEditor = vscode.window.visibleTextEditors.find( + editor => { + const unescapedUri = unescape( + editor.document.uri.toString(), + ); + // Unescaped URI looks like: + // file:///c:/Workspace/ra-test/src/main.rs + return unescapedUri === params.uri; + }, + ); + if (!targetEditor) return; + + highlighter.setHighlights(targetEditor, params.decorations); + }, + ); + }) vscode.workspace.onDidChangeConfiguration( _ => highlighter.removeHighlights(), @@ -40,11 +41,14 @@ export function activateHighlighting(ctx: Ctx) { async (editor: vscode.TextEditor | undefined) => { if (!editor || editor.document.languageId !== 'rust') return; if (!ctx.config.highlightingOn) return; + let client = ctx.client; + if (!client) return; const params: lc.TextDocumentIdentifier = { uri: editor.document.uri.toString(), }; - const decorations = await ctx.sendRequestWithRetry( + const decorations = await sendRequestWithRetry( + client, 'rust-analyzer/decorationsRequest', params, ); @@ -103,6 +107,8 @@ class Highlighter { } public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) { + let client = this.ctx.client; + if (!client) return; // Initialize decorations if necessary // // Note: decoration objects need to be kept around so we can dispose them @@ -135,13 +141,13 @@ class Highlighter { colorfulIdents .get(d.bindingHash)![0] .push( - this.ctx.client.protocol2CodeConverter.asRange(d.range), + client.protocol2CodeConverter.asRange(d.range), ); } else { byTag .get(d.tag)! .push( - this.ctx.client.protocol2CodeConverter.asRange(d.range), + client.protocol2CodeConverter.asRange(d.range), ); } } 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 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; -import { Ctx } from './ctx'; +import { Ctx, sendRequestWithRetry } from './ctx'; export function activateInlayHints(ctx: Ctx) { const hintsUpdater = new HintsUpdater(ctx); @@ -19,9 +19,7 @@ export function activateInlayHints(ctx: Ctx) { hintsUpdater.setEnabled(ctx.config.displayInlayHints); }, ctx.subscriptions); - // XXX: don't await here; - // Who knows what happens if an exception is thrown here... - hintsUpdater.refresh(); + ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints)) } interface InlayHintsParams { @@ -97,6 +95,8 @@ class HintsUpdater { } private async queryHints(documentUri: string): Promise { + let client = this.ctx.client; + if (!client) return null const request: InlayHintsParams = { textDocument: { uri: documentUri }, }; @@ -105,7 +105,8 @@ class HintsUpdater { if (prev) prev.cancel(); this.pending.set(documentUri, tokenSource); try { - return await this.ctx.sendRequestWithRetry( + return await sendRequestWithRetry( + client, 'rust-analyzer/inlayHints', request, tokenSource.token, diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 3d9107927..22450060b 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -3,7 +3,6 @@ import * as vscode from 'vscode'; import * as commands from './commands'; import { activateInlayHints } from './inlay_hints'; import { activateStatusDisplay } from './status_display'; -import { Server } from './server'; import { Ctx } from './ctx'; import { activateHighlighting } from './highlighting'; @@ -21,6 +20,7 @@ export async function activate(context: vscode.ExtensionContext) { ctx.registerCommand('syntaxTree', commands.syntaxTree); ctx.registerCommand('expandMacro', commands.expandMacro); ctx.registerCommand('run', commands.run); + ctx.registerCommand('reload', commands.reload); // Internal commands which are invoked by the server. ctx.registerCommand('runSingle', commands.runSingle); @@ -30,38 +30,17 @@ export async function activate(context: vscode.ExtensionContext) { if (ctx.config.enableEnhancedTyping) { ctx.overrideCommand('type', commands.onEnter); } - - const startServer = () => Server.start(ctx.config); - const reloadCommand = () => reloadServer(startServer); - - vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); - + activateStatusDisplay(ctx); + activateHighlighting(ctx); + activateInlayHints(ctx); // Start the language server, finally! try { - await startServer(); + await ctx.restartServer(); } catch (e) { vscode.window.showErrorMessage(e.message); } - - activateStatusDisplay(ctx); - activateHighlighting(ctx); - - if (ctx.config.displayInlayHints) { - activateInlayHints(ctx); - } } -export function deactivate(): Thenable { - if (!Server.client) { - return Promise.resolve(); - } - return Server.client.stop(); -} - -async function reloadServer(startServer: () => Promise) { - if (Server.client != null) { - vscode.window.showInformationMessage('Reloading rust-analyzer...'); - await Server.client.stop(); - await startServer(); - } +export async function deactivate() { + await ctx?.client?.stop(); } diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts deleted file mode 100644 index ab9f3bfa6..000000000 --- a/editors/code/src/server.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { homedir } from 'os'; -import * as lc from 'vscode-languageclient'; - -import { window, workspace } from 'vscode'; -import { Config } from './config'; - -function expandPathResolving(path: string) { - if (path.startsWith('~/')) { - return path.replace('~', homedir()); - } - return path; -} - -export class Server { - static config: Config; - public static client: lc.LanguageClient; - - public static async start(config: Config) { - // '.' Is the fallback if no folder is open - // 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. - let folder: string = '.'; - if (workspace.workspaceFolders !== undefined) { - folder = workspace.workspaceFolders[0].uri.fsPath.toString(); - } - - this.config = config; - const command = expandPathResolving(this.config.raLspServerPath); - const run: lc.Executable = { - command, - options: { cwd: folder }, - }; - const serverOptions: lc.ServerOptions = { - run, - debug: run, - }; - const traceOutputChannel = window.createOutputChannel( - 'Rust Analyzer Language Server Trace', - ); - const clientOptions: lc.LanguageClientOptions = { - documentSelector: [{ scheme: 'file', language: 'rust' }], - initializationOptions: { - publishDecorations: true, - lruCapacity: Server.config.lruCapacity, - maxInlayHintLength: Server.config.maxInlayHintLength, - cargoWatchEnable: Server.config.cargoWatchOptions.enable, - cargoWatchArgs: Server.config.cargoWatchOptions.arguments, - cargoWatchCommand: Server.config.cargoWatchOptions.command, - cargoWatchAllTargets: - Server.config.cargoWatchOptions.allTargets, - excludeGlobs: Server.config.excludeGlobs, - useClientWatching: Server.config.useClientWatching, - featureFlags: Server.config.featureFlags, - withSysroot: Server.config.withSysroot, - cargoFeatures: Server.config.cargoFeatures, - }, - traceOutputChannel, - }; - - Server.client = new lc.LanguageClient( - 'rust-analyzer', - 'Rust Analyzer Language Server', - serverOptions, - clientOptions, - ); - // HACK: This is an awful way of filtering out the decorations notifications - // However, pending proper support, this is the most effecitve approach - // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages - // Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting) - // This also requires considering our settings strategy, which is work which needs doing - // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests - Server.client._tracer = { - log: (messageOrDataObject: string | any, data?: string) => { - if (typeof messageOrDataObject === 'string') { - if ( - messageOrDataObject.includes( - 'rust-analyzer/publishDecorations', - ) || - messageOrDataObject.includes( - 'rust-analyzer/decorationsRequest', - ) - ) { - // Don't log publish decorations requests - } else { - // @ts-ignore This is just a utility function - Server.client.logTrace(messageOrDataObject, data); - } - } else { - // @ts-ignore - Server.client.logObjectTrace(messageOrDataObject); - } - }, - }; - Server.client.registerProposedFeatures(); - Server.client.start(); - } -} 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 { } export async function applySourceChange(ctx: Ctx, change: SourceChange) { - const wsEdit = ctx.client.protocol2CodeConverter.asWorkspaceEdit( + const client = ctx.client; + if (!client) return + + const wsEdit = client.protocol2CodeConverter.asWorkspaceEdit( change.workspaceEdit, ); let created; @@ -32,10 +35,10 @@ export async function applySourceChange(ctx: Ctx, change: SourceChange) { const doc = await vscode.workspace.openTextDocument(toOpenUri); await vscode.window.showTextDocument(doc); } else if (toReveal) { - const uri = ctx.client.protocol2CodeConverter.asUri( + const uri = client.protocol2CodeConverter.asUri( toReveal.textDocument.uri, ); - const position = ctx.client.protocol2CodeConverter.asPosition( + const position = client.protocol2CodeConverter.asPosition( toReveal.position, ); const editor = vscode.window.activeTextEditor; diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts index e3719075b..1454bf8b0 100644 --- a/editors/code/src/status_display.ts +++ b/editors/code/src/status_display.ts @@ -7,7 +7,9 @@ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', ' export function activateStatusDisplay(ctx: Ctx) { const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); ctx.pushCleanup(statusDisplay); - ctx.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params)); + ctx.onDidRestart(client => { + client.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params)); + }) } class StatusDisplay implements vscode.Disposable { -- cgit v1.2.3 From 6368b40dd98b208da3758d4d1eed34cf276e3b09 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 31 Dec 2019 18:38:22 +0100 Subject: Work around synchrnonisation issue --- editors/code/src/main.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 22450060b..51dedd5ef 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -11,6 +11,17 @@ let ctx!: Ctx; export async function activate(context: vscode.ExtensionContext) { ctx = new Ctx(context); + // Note: we try to start the server before we register various commands, so + // that it registers its `onDidChangeDocument` handler before us. + // + // This a horribly, horribly wrong way to deal with this problem. + try { + await ctx.restartServer(); + } catch (e) { + vscode.window.showErrorMessage(e.message); + } + + // Commands which invokes manually via command pallet, shortcut, etc. ctx.registerCommand('analyzerStatus', commands.analyzerStatus); ctx.registerCommand('collectGarbage', commands.collectGarbage); @@ -33,12 +44,6 @@ export async function activate(context: vscode.ExtensionContext) { activateStatusDisplay(ctx); activateHighlighting(ctx); activateInlayHints(ctx); - // Start the language server, finally! - try { - await ctx.restartServer(); - } catch (e) { - vscode.window.showErrorMessage(e.message); - } } export async function deactivate() { -- cgit v1.2.3