Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'editors/code/src/ctx.ts')
| -rw-r--r-- | editors/code/src/ctx.ts | 176 |
1 files changed, 130 insertions, 46 deletions
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 26510011d4..e94d4365c3 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 = | { @@ -17,65 +19,140 @@ export type Workspace = }; 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 state: PersistentState; + + 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({ + dispose() { + this.dispose(); + }, + }); + this.statusBar.text = "rust-analyzer"; + this.statusBar.tooltip = "ready"; + this.statusBar.command = "rust-analyzer.analyzerStatus"; + this.statusBar.show(); + this.workspace = workspace; + + this.state = new PersistentState(extCtx.globalState); + this.config = new Config(extCtx); } - get activeRustEditor(): RustEditor | undefined { - const editor = vscode.window.activeTextEditor; - return editor && isRustEditor(editor) ? editor : undefined; + clientFetcher() { + return { + get client(): lc.LanguageClient | undefined { + return this.client; + }, + }; } - get visibleRustEditors(): RustEditor[] { - return vscode.window.visibleTextEditors.filter(isRustEditor); + async getClient() { + // if server path changes -> dispose + 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.client.onNotification(ra.serverStatus, (params) => this.setServerStatus(params)); + } + return this.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 activate() { + log.info("Activating language client"); + const client = await this.getClient(); + await client.start(); + return client; } - get extensionPath(): string { - return this.extCtx.extensionPath; + async deactivate() { + log.info("Deactivating language client"); + await this.client?.stop(); + } + + async disposeClient() { + log.info("Deactivating language client"); + await this.client?.dispose(); + this._serverPath = undefined; + this.client = undefined; } - get globalState(): vscode.Memento { - return this.extCtx.globalState; + get activeRustEditor(): RustEditor | undefined { + const editor = vscode.window.activeTextEditor; + return editor && isRustEditor(editor) ? editor : undefined; + } + + get extensionPath(): string { + return this.extCtx.extensionPath; } get subscriptions(): Disposable[] { return this.extCtx.subscriptions; } + get serverPath(): string | undefined { + return this._serverPath; + } + setServerStatus(status: ServerStatusParams) { let icon = ""; const statusBar = this.statusBar; @@ -111,7 +188,14 @@ export class Ctx { statusBar.text = `${icon}rust-analyzer`; } - pushCleanup(d: Disposable) { + registerCommand(name: string, factory: (ctx: Ctx) => Cmd) { + const fullName = `rust-analyzer.${name}`; + const cmd = factory(this); + const d = vscode.commands.registerCommand(fullName, cmd); + this.pushExtCleanup(d); + } + + pushExtCleanup(d: Disposable) { this.extCtx.subscriptions.push(d); } } |