diff options
| author | Laurențiu Nicola <lnicola@dend.ro> | 2022-10-26 17:40:41 +0300 |
|---|---|---|
| committer | Laurențiu Nicola <lnicola@dend.ro> | 2022-10-26 17:40:41 +0300 |
| commit | 8807fc4cc358fa2152b303df0caa2f5fc9efaa9d (patch) | |
| tree | da701483c74d2d402617c2482626b70aa2fff536 /editors/code/src/ctx.ts | |
| parent | 26a413e015b7fef4c0edb51bda0d39ab499f4950 (diff) | |
| download | rust-8807fc4cc358fa2152b303df0caa2f5fc9efaa9d.tar.gz rust-8807fc4cc358fa2152b303df0caa2f5fc9efaa9d.zip | |
:arrow_up: rust-analyzer
Diffstat (limited to 'editors/code/src/ctx.ts')
| -rw-r--r-- | editors/code/src/ctx.ts | 224 |
1 files changed, 178 insertions, 46 deletions
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 26510011d43..044a9470aa9 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -2,10 +2,12 @@ import * as vscode from "vscode"; import * as lc from "vscode-languageclient/node"; import * as ra from "./lsp_ext"; -import { Config } from "./config"; +import { Config, substituteVariablesInEnv, substituteVSCodeVariables } from "./config"; import { createClient } from "./client"; -import { isRustEditor, RustEditor } from "./util"; +import { isRustEditor, log, RustEditor } from "./util"; import { ServerStatusParams } from "./lsp_ext"; +import { PersistentState } from "./persistent_state"; +import { bootstrap } from "./bootstrap"; export type Workspace = | { @@ -16,66 +18,192 @@ export type Workspace = files: vscode.TextDocument[]; }; +export type CommandFactory = { + enabled: (ctx: Ctx) => Cmd; + disabled?: (ctx: Ctx) => Cmd; +}; + export class Ctx { - private constructor( - readonly config: Config, - private readonly extCtx: vscode.ExtensionContext, - readonly client: lc.LanguageClient, - readonly serverPath: string, - readonly statusBar: vscode.StatusBarItem - ) {} - - static async create( - config: Config, - extCtx: vscode.ExtensionContext, - serverPath: string, - workspace: Workspace - ): Promise<Ctx> { - const client = await createClient(serverPath, workspace, config.serverExtraEnv); - - const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); - extCtx.subscriptions.push(statusBar); - statusBar.text = "rust-analyzer"; - statusBar.tooltip = "ready"; - statusBar.command = "rust-analyzer.analyzerStatus"; - statusBar.show(); - - const res = new Ctx(config, extCtx, client, serverPath, statusBar); - - res.pushCleanup(client.start()); - await client.onReady(); - client.onNotification(ra.serverStatus, (params) => res.setServerStatus(params)); - return res; + readonly statusBar: vscode.StatusBarItem; + readonly config: Config; + + private client: lc.LanguageClient | undefined; + private _serverPath: string | undefined; + private traceOutputChannel: vscode.OutputChannel | undefined; + private outputChannel: vscode.OutputChannel | undefined; + private clientSubscriptions: Disposable[]; + private state: PersistentState; + private commandFactories: Record<string, CommandFactory>; + private commandDisposables: Disposable[]; + + workspace: Workspace; + + constructor( + readonly extCtx: vscode.ExtensionContext, + workspace: Workspace, + commandFactories: Record<string, CommandFactory> + ) { + extCtx.subscriptions.push(this); + this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + this.statusBar.text = "rust-analyzer"; + this.statusBar.tooltip = "ready"; + this.statusBar.command = "rust-analyzer.analyzerStatus"; + this.statusBar.show(); + this.workspace = workspace; + this.clientSubscriptions = []; + this.commandDisposables = []; + this.commandFactories = commandFactories; + + this.state = new PersistentState(extCtx.globalState); + this.config = new Config(extCtx); + + this.updateCommands(); } - get activeRustEditor(): RustEditor | undefined { - const editor = vscode.window.activeTextEditor; - return editor && isRustEditor(editor) ? editor : undefined; + dispose() { + this.config.dispose(); + this.statusBar.dispose(); + void this.disposeClient(); + this.commandDisposables.forEach((disposable) => disposable.dispose()); } - get visibleRustEditors(): RustEditor[] { - return vscode.window.visibleTextEditors.filter(isRustEditor); + clientFetcher() { + const self = this; + return { + get client(): lc.LanguageClient | undefined { + return self.client; + }, + }; } - registerCommand(name: string, factory: (ctx: Ctx) => Cmd) { - const fullName = `rust-analyzer.${name}`; - const cmd = factory(this); - const d = vscode.commands.registerCommand(fullName, cmd); - this.pushCleanup(d); + async getClient() { + if (!this.traceOutputChannel) { + this.traceOutputChannel = vscode.window.createOutputChannel( + "Rust Analyzer Language Server Trace" + ); + this.pushExtCleanup(this.traceOutputChannel); + } + if (!this.outputChannel) { + this.outputChannel = vscode.window.createOutputChannel("Rust Analyzer Language Server"); + this.pushExtCleanup(this.outputChannel); + } + + if (!this.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) + ); + const run: lc.Executable = { + command: this._serverPath, + options: { env: newEnv }, + }; + const serverOptions = { + run, + debug: run, + }; + + let rawInitializationOptions = vscode.workspace.getConfiguration("rust-analyzer"); + + if (this.workspace.kind === "Detached Files") { + rawInitializationOptions = { + detachedFiles: this.workspace.files.map((file) => file.uri.fsPath), + ...rawInitializationOptions, + }; + } + + const initializationOptions = substituteVSCodeVariables(rawInitializationOptions); + + this.client = await createClient( + this.traceOutputChannel, + this.outputChannel, + initializationOptions, + serverOptions + ); + this.pushClientCleanup( + this.client.onNotification(ra.serverStatus, (params) => + this.setServerStatus(params) + ) + ); + } + return this.client; } - get extensionPath(): string { - return this.extCtx.extensionPath; + async activate() { + log.info("Activating language client"); + const client = await this.getClient(); + await client.start(); + this.updateCommands(); + return client; + } + + async deactivate() { + log.info("Deactivating language client"); + await this.client?.stop(); + this.updateCommands(); + } + + async stop() { + log.info("Stopping language client"); + await this.disposeClient(); + this.updateCommands(); + } + + private async disposeClient() { + this.clientSubscriptions?.forEach((disposable) => disposable.dispose()); + this.clientSubscriptions = []; + await this.client?.dispose(); + this._serverPath = undefined; + this.client = undefined; + } + + get activeRustEditor(): RustEditor | undefined { + const editor = vscode.window.activeTextEditor; + return editor && isRustEditor(editor) ? editor : undefined; } - get globalState(): vscode.Memento { - return this.extCtx.globalState; + get extensionPath(): string { + return this.extCtx.extensionPath; } get subscriptions(): Disposable[] { return this.extCtx.subscriptions; } + get serverPath(): string | undefined { + return this._serverPath; + } + + private updateCommands() { + this.commandDisposables.forEach((disposable) => disposable.dispose()); + this.commandDisposables = []; + const fetchFactory = (factory: CommandFactory, fullName: string) => { + return this.client && this.client.isRunning() + ? factory.enabled + : factory.disabled || + ((_) => () => + vscode.window.showErrorMessage( + `command ${fullName} failed: rust-analyzer server is not running` + )); + }; + for (const [name, factory] of Object.entries(this.commandFactories)) { + const fullName = `rust-analyzer.${name}`; + const callback = fetchFactory(factory, fullName)(this); + this.commandDisposables.push(vscode.commands.registerCommand(fullName, callback)); + } + } + setServerStatus(status: ServerStatusParams) { let icon = ""; const statusBar = this.statusBar; @@ -111,9 +239,13 @@ export class Ctx { statusBar.text = `${icon}rust-analyzer`; } - pushCleanup(d: Disposable) { + pushExtCleanup(d: Disposable) { this.extCtx.subscriptions.push(d); } + + private pushClientCleanup(d: Disposable) { + this.clientSubscriptions.push(d); + } } export interface Disposable { |
