import * as vscode from 'vscode';
-import * as lc from 'vscode-languageclient';
+import * as lc from 'vscode-languageclient/node';
+import * as ra from './lsp_ext';
import { Config } from './config';
import { createClient } from './client';
+import { isRustEditor, RustEditor } from './util';
+import { ServerStatusParams } from './lsp_ext';
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.
- // FIXME: this actually needs syncronization of some kind (check how
- // vscode deals with `deactivate()` call when extension has some work scheduled
- // on the event loop to get a better picture of what we can do here)
- client: lc.LanguageClient | null = null;
- private extCtx: vscode.ExtensionContext;
- private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = [];
+ private constructor(
+ readonly config: Config,
+ private readonly extCtx: vscode.ExtensionContext,
+ readonly client: lc.LanguageClient,
+ readonly serverPath: string,
+ readonly statusBar: vscode.StatusBarItem,
+ ) {
- constructor(extCtx: vscode.ExtensionContext) {
- this.config = new Config(extCtx);
- this.extCtx = extCtx;
}
- async restartServer() {
- const old = this.client;
- if (old) {
- await old.stop();
- }
- this.client = null;
- const client = await createClient(this.config);
- if (!client) {
- throw new Error(
- "Rust Analyzer Language Server is not available. " +
- "Please, ensure its [proper installation](https://github.com/rust-analyzer/rust-analyzer/tree/master/docs/user#vs-code)."
- );
- }
+ static async create(
+ config: Config,
+ extCtx: vscode.ExtensionContext,
+ serverPath: string,
+ cwd: string,
+ ): Promise<Ctx> {
+ const client = createClient(serverPath, cwd, config.serverExtraEnv);
- this.pushCleanup(client.start());
- await client.onReady();
+ const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
+ extCtx.subscriptions.push(statusBar);
+ statusBar.text = "rust-analyzer";
+ statusBar.tooltip = "ready";
+ statusBar.show();
- this.client = client;
- for (const hook of this.onDidRestartHooks) {
- hook(client);
- }
+ 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;
}
- get activeRustEditor(): vscode.TextEditor | undefined {
+ get activeRustEditor(): RustEditor | undefined {
const editor = vscode.window.activeTextEditor;
- return editor && editor.document.languageId === 'rust'
+ return editor && isRustEditor(editor)
? editor
: undefined;
}
+ get visibleRustEditors(): RustEditor[] {
+ return vscode.window.visibleTextEditors.filter(isRustEditor);
+ }
+
registerCommand(name: string, factory: (ctx: Ctx) => Cmd) {
const fullName = `rust-analyzer.${name}`;
const cmd = factory(this);
return this.extCtx.subscriptions;
}
- pushCleanup(d: Disposable) {
- this.extCtx.subscriptions.push(d);
+ setServerStatus(status: ServerStatusParams) {
+ this.statusBar.tooltip = status.message ?? "Ready";
+ let icon = "";
+ switch (status.health) {
+ case "ok":
+ this.statusBar.color = undefined;
+ break;
+ case "warning":
+ this.statusBar.tooltip += "\nClick to reload."
+ this.statusBar.command = "rust-analyzer.reloadWorkspace";
+ this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground");
+ icon = "$(warning) ";
+ break;
+ case "error":
+ this.statusBar.tooltip += "\nClick to reload."
+ this.statusBar.command = "rust-analyzer.reloadWorkspace";
+ this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground");
+ icon = "$(error) ";
+ break;
+ }
+ if (!status.quiescent) icon = "$(sync~spin) ";
+ this.statusBar.text = `${icon} rust-analyzer`;
}
- onDidRestart(hook: (client: lc.LanguageClient) => void) {
- this.onDidRestartHooks.push(hook);
+ pushCleanup(d: Disposable) {
+ this.extCtx.subscriptions.push(d);
}
}
dispose(): void;
}
export type Cmd = (...args: any[]) => unknown;
-
-export async function sendRequestWithRetry<R>(
- client: lc.LanguageClient,
- method: string,
- param: unknown,
- token?: vscode.CancellationToken,
-): Promise<R> {
- 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));