1 import * as vscode from "vscode";
2 import * as lc from "vscode-languageclient/node";
3 import * as ra from "./lsp_ext";
5 import { Config, substituteVariablesInEnv, substituteVSCodeVariables } from "./config";
6 import { createClient } from "./client";
7 import { isRustEditor, log, RustEditor } from "./util";
8 import { ServerStatusParams } from "./lsp_ext";
9 import { PersistentState } from "./persistent_state";
10 import { bootstrap } from "./bootstrap";
12 export type Workspace =
14 kind: "Workspace Folder";
17 kind: "Detached Files";
18 files: vscode.TextDocument[];
21 export type CommandFactory = {
22 enabled: (ctx: Ctx) => Cmd;
23 disabled?: (ctx: Ctx) => Cmd;
27 readonly statusBar: vscode.StatusBarItem;
28 readonly config: Config;
30 private client: lc.LanguageClient | undefined;
31 private _serverPath: string | undefined;
32 private traceOutputChannel: vscode.OutputChannel | undefined;
33 private outputChannel: vscode.OutputChannel | undefined;
34 private clientSubscriptions: Disposable[];
35 private state: PersistentState;
36 private commandFactories: Record<string, CommandFactory>;
37 private commandDisposables: Disposable[];
42 readonly extCtx: vscode.ExtensionContext,
44 commandFactories: Record<string, CommandFactory>
46 extCtx.subscriptions.push(this);
47 this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
48 this.statusBar.text = "rust-analyzer";
49 this.statusBar.tooltip = "ready";
50 this.statusBar.command = "rust-analyzer.analyzerStatus";
51 this.statusBar.show();
52 this.workspace = workspace;
53 this.clientSubscriptions = [];
54 this.commandDisposables = [];
55 this.commandFactories = commandFactories;
57 this.state = new PersistentState(extCtx.globalState);
58 this.config = new Config(extCtx);
60 this.updateCommands();
64 this.config.dispose();
65 this.statusBar.dispose();
66 void this.disposeClient();
67 this.commandDisposables.forEach((disposable) => disposable.dispose());
73 get client(): lc.LanguageClient | undefined {
80 if (!this.traceOutputChannel) {
81 this.traceOutputChannel = vscode.window.createOutputChannel(
82 "Rust Analyzer Language Server Trace"
84 this.pushExtCleanup(this.traceOutputChannel);
86 if (!this.outputChannel) {
87 this.outputChannel = vscode.window.createOutputChannel("Rust Analyzer Language Server");
88 this.pushExtCleanup(this.outputChannel);
92 this._serverPath = await bootstrap(this.extCtx, this.config, this.state).catch(
94 let message = "bootstrap error. ";
97 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). ';
99 'To enable verbose logs use { "rust-analyzer.trace.extension": true }';
101 log.error("Bootstrap error", err);
102 throw new Error(message);
105 const newEnv = substituteVariablesInEnv(
106 Object.assign({}, process.env, this.config.serverExtraEnv)
108 const run: lc.Executable = {
109 command: this._serverPath,
110 options: { env: newEnv },
112 const serverOptions = {
117 let rawInitializationOptions = vscode.workspace.getConfiguration("rust-analyzer");
119 if (this.workspace.kind === "Detached Files") {
120 rawInitializationOptions = {
121 detachedFiles: this.workspace.files.map((file) => file.uri.fsPath),
122 ...rawInitializationOptions,
126 const initializationOptions = substituteVSCodeVariables(rawInitializationOptions);
128 this.client = await createClient(
129 this.traceOutputChannel,
131 initializationOptions,
134 this.pushClientCleanup(
135 this.client.onNotification(ra.serverStatus, (params) =>
136 this.setServerStatus(params)
144 log.info("Activating language client");
145 const client = await this.getClient();
146 await client.start();
147 this.updateCommands();
152 log.info("Deactivating language client");
153 await this.client?.stop();
154 this.updateCommands();
158 log.info("Stopping language client");
159 await this.disposeClient();
160 this.updateCommands();
163 private async disposeClient() {
164 this.clientSubscriptions?.forEach((disposable) => disposable.dispose());
165 this.clientSubscriptions = [];
166 await this.client?.dispose();
167 this._serverPath = undefined;
168 this.client = undefined;
171 get activeRustEditor(): RustEditor | undefined {
172 const editor = vscode.window.activeTextEditor;
173 return editor && isRustEditor(editor) ? editor : undefined;
176 get extensionPath(): string {
177 return this.extCtx.extensionPath;
180 get subscriptions(): Disposable[] {
181 return this.extCtx.subscriptions;
184 get serverPath(): string | undefined {
185 return this._serverPath;
188 private updateCommands() {
189 this.commandDisposables.forEach((disposable) => disposable.dispose());
190 this.commandDisposables = [];
191 const fetchFactory = (factory: CommandFactory, fullName: string) => {
192 return this.client && this.client.isRunning()
194 : factory.disabled ||
196 vscode.window.showErrorMessage(
197 `command ${fullName} failed: rust-analyzer server is not running`
200 for (const [name, factory] of Object.entries(this.commandFactories)) {
201 const fullName = `rust-analyzer.${name}`;
202 const callback = fetchFactory(factory, fullName)(this);
203 this.commandDisposables.push(vscode.commands.registerCommand(fullName, callback));
207 setServerStatus(status: ServerStatusParams) {
209 const statusBar = this.statusBar;
210 switch (status.health) {
212 statusBar.tooltip = status.message ?? "Ready";
213 statusBar.command = undefined;
214 statusBar.color = undefined;
215 statusBar.backgroundColor = undefined;
219 (status.message ? status.message + "\n" : "") + "Click to reload.";
221 statusBar.command = "rust-analyzer.reloadWorkspace";
222 statusBar.color = new vscode.ThemeColor("statusBarItem.warningForeground");
223 statusBar.backgroundColor = new vscode.ThemeColor(
224 "statusBarItem.warningBackground"
226 icon = "$(warning) ";
230 (status.message ? status.message + "\n" : "") + "Click to reload.";
232 statusBar.command = "rust-analyzer.reloadWorkspace";
233 statusBar.color = new vscode.ThemeColor("statusBarItem.errorForeground");
234 statusBar.backgroundColor = new vscode.ThemeColor("statusBarItem.errorBackground");
238 if (!status.quiescent) icon = "$(sync~spin) ";
239 statusBar.text = `${icon}rust-analyzer`;
242 pushExtCleanup(d: Disposable) {
243 this.extCtx.subscriptions.push(d);
246 private pushClientCleanup(d: Disposable) {
247 this.clientSubscriptions.push(d);
251 export interface Disposable {
254 export type Cmd = (...args: any[]) => unknown;