diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index cbdeb28c99..cb088fc6c6 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -310,6 +310,10 @@ export function ssr(ctx: Ctx): Cmd { export function serverVersion(ctx: Ctx): Cmd { return async () => { + if (!ctx.serverPath) { + void vscode.window.showWarningMessage(`rust-analyzer server is not running`); + return; + } const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" }); const versionString = stdout.slice(`rust-analyzer `.length).trim(); diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 199db6e30f..10e243dc89 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -14,8 +14,6 @@ export class Config { readonly rootSection = "rust-analyzer"; private readonly requiresWorkspaceReloadOpts = [ - "serverPath", - "server", // FIXME: This shouldn't be here, changing this setting should reload // `continueCommentsOnNewline` behavior without restart "typing", @@ -23,6 +21,8 @@ export class Config { private readonly requiresReloadOpts = [ "cargo", "procMacro", + "serverPath", + "server", "files", "lens", // works as lens.* ] diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index d4f5ab3c88..f62ccd1a65 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -6,6 +6,8 @@ import { Config, substituteVariablesInEnv, substituteVSCodeVariables } from "./c import { createClient } from "./client"; import { isRustEditor, log, RustEditor } from "./util"; import { ServerStatusParams } from "./lsp_ext"; +import { PersistentState } from "./persistent_state"; +import { bootstrap } from "./bootstrap"; export type Workspace = | { @@ -17,28 +19,18 @@ export type Workspace = }; export class Ctx { - private client: lc.LanguageClient | undefined; - readonly config: Config; - serverPath: string; readonly statusBar: vscode.StatusBarItem; + readonly config: Config; + + private client: lc.LanguageClient | undefined; traceOutputChannel: vscode.OutputChannel | undefined; outputChannel: vscode.OutputChannel | undefined; - - serverOptions: - | { - run: lc.Executable; - debug: lc.Executable; - } - | undefined; workspace: Workspace; + state: PersistentState; + serverPath: string | undefined; - constructor( - readonly extCtx: vscode.ExtensionContext, - config: Config, - serverPath: string, - workspace: Workspace - ) { + constructor(readonly extCtx: vscode.ExtensionContext, workspace: Workspace) { this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); extCtx.subscriptions.push(this.statusBar); extCtx.subscriptions.push({ @@ -50,9 +42,10 @@ export class Ctx { this.statusBar.tooltip = "ready"; this.statusBar.command = "rust-analyzer.analyzerStatus"; this.statusBar.show(); - this.serverPath = serverPath; - this.config = config; this.workspace = workspace; + + this.state = new PersistentState(extCtx.globalState); + this.config = new Config(extCtx); } clientFetcher() { @@ -64,6 +57,7 @@ export class Ctx { } async getClient() { + // if server path changes -> dispose if (!this.traceOutputChannel) { this.traceOutputChannel = vscode.window.createOutputChannel( "Rust Analyzer Language Server Trace" @@ -72,8 +66,20 @@ export class Ctx { if (!this.outputChannel) { this.outputChannel = vscode.window.createOutputChannel("Rust Analyzer Language Server"); } - if (!this.serverOptions) { - log.info("Creating server options client"); + + if (!this.client) { + log.info("Creating language client"); + + this.serverPath = await bootstrap(this.extCtx, this.config, this.state).catch((err) => { + let message = "bootstrap error. "; + + message += + 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). '; + message += 'To enable verbose logs use { "rust-analyzer.trace.extension": true }'; + + log.error("Bootstrap error", err); + throw new Error(message); + }); const newEnv = substituteVariablesInEnv( Object.assign({}, process.env, this.config.serverExtraEnv) ); @@ -81,16 +87,11 @@ export class Ctx { command: this.serverPath, options: { env: newEnv }, }; - this.serverOptions = { + const serverOptions = { run, debug: run, }; - } else { - this.serverOptions.run.command = this.serverPath; - this.serverOptions.debug.command = this.serverPath; - } - if (!this.client) { - log.info("Creating language client"); + let rawInitializationOptions = vscode.workspace.getConfiguration("rust-analyzer"); if (this.workspace.kind === "Detached Files") { @@ -106,7 +107,7 @@ export class Ctx { this.traceOutputChannel, this.outputChannel, initializationOptions, - this.serverOptions + serverOptions ); this.client.onNotification(ra.serverStatus, (params) => this.setServerStatus(params)); } @@ -128,6 +129,7 @@ export class Ctx { async disposeClient() { log.info("Deactivating language client"); await this.client?.dispose(); + this.serverPath = undefined; this.client = undefined; } diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 4ff27e0b55..84f0a5810f 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -3,12 +3,9 @@ import * as lc from "vscode-languageclient/node"; import * as commands from "./commands"; import { Ctx, Workspace } from "./ctx"; -import { log, isRustDocument } from "./util"; -import { PersistentState } from "./persistent_state"; +import { isRustDocument } from "./util"; import { activateTaskProvider } from "./tasks"; import { setContextValue } from "./util"; -import { bootstrap } from "./bootstrap"; -import { Config } from "./config"; const RUST_PROJECT_CONTEXT_NAME = "inRustProject"; @@ -56,24 +53,13 @@ export async function activate( } : { kind: "Workspace Folder" }; - const state = new PersistentState(context.globalState); - const config = new Config(context); - - const serverPath = await bootstrap(context, config, state).catch((err) => { - let message = "bootstrap error. "; - - message += 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). '; - message += 'To enable verbose logs use { "rust-analyzer.trace.extension": true }'; - - log.error("Bootstrap error", err); - throw new Error(message); - }); - - const ctx = new Ctx(context, config, serverPath, workspace); + const ctx = new Ctx(context, workspace); // VS Code doesn't show a notification when an extension fails to activate // so we do it ourselves. return await activateServer(ctx).catch((err) => { - void vscode.window.showErrorMessage(`Cannot activate rust-analyzer: ${err.message}`); + void vscode.window.showErrorMessage( + `Cannot activate rust-analyzer extension: ${err.message}` + ); throw err; }); } @@ -83,7 +69,6 @@ async function activateServer(ctx: Ctx): Promise { ctx.pushExtCleanup(activateTaskProvider(ctx.config)); } - await ctx.activate(); await initCommonContext(ctx); if (ctx.config.typingContinueCommentsOnNewline) { @@ -91,17 +76,19 @@ async function activateServer(ctx: Ctx): Promise { } vscode.workspace.onDidChangeConfiguration( - (_) => - ctx - .getClient() - .then((it) => - it.sendNotification("workspace/didChangeConfiguration", { settings: "" }) - ) - .catch(log.error), + async (_) => { + await ctx + .clientFetcher() + .client?.sendNotification("workspace/didChangeConfiguration", { settings: "" }); + }, null, ctx.subscriptions ); + await ctx.activate().catch((err) => { + void vscode.window.showErrorMessage(`Cannot activate rust-analyzer server: ${err.message}`); + }); + return ctx.clientFetcher(); } @@ -130,6 +117,7 @@ async function initCommonContext(ctx: Ctx) { // Commands which invokes manually via command palette, shortcut, etc. ctx.registerCommand("reload", (_) => async () => { void vscode.window.showInformationMessage("Reloading rust-analyzer..."); + // FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed await ctx.disposeClient(); await ctx.activate(); });