From 3ef76760761d17cef4ea4e8462d9ee2ca8395467 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 2 Jul 2020 12:37:04 +0200 Subject: Implement StatusBar --- crates/rust-analyzer/src/config.rs | 2 ++ crates/rust-analyzer/src/global_state.rs | 1 + crates/rust-analyzer/src/lsp_ext.rs | 18 ++++++++++++- crates/rust-analyzer/src/main_loop.rs | 18 ++++++++++--- docs/dev/lsp-extensions.md | 12 +++++++++ editors/code/src/client.ts | 1 + editors/code/src/ctx.ts | 43 +++++++++++++++++++++++++++++++- editors/code/src/lsp_ext.ts | 3 +++ 8 files changed, 93 insertions(+), 5 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 6c311648a..21acfe644 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -130,6 +130,7 @@ pub struct ClientCapsConfig { pub code_action_group: bool, pub resolve_code_action: bool, pub hover_actions: bool, + pub status_notification: bool, } impl Config { @@ -365,6 +366,7 @@ impl Config { self.client_caps.code_action_group = get_bool("codeActionGroup"); self.client_caps.resolve_code_action = get_bool("resolveCodeAction"); self.client_caps.hover_actions = get_bool("hoverActions"); + self.client_caps.status_notification = get_bool("statusNotification"); } } } diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index b7b4edf66..5e9cae3f8 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -31,6 +31,7 @@ use crate::{ pub(crate) enum Status { Loading, Ready, + Invalid, } impl Default for Status { diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 82207bbb8..d225ad5ff 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, path::PathBuf}; use lsp_types::request::Request; -use lsp_types::{Position, Range, TextDocumentIdentifier}; +use lsp_types::{notification::Notification, Position, Range, TextDocumentIdentifier}; use serde::{Deserialize, Serialize}; pub enum AnalyzerStatus {} @@ -208,6 +208,22 @@ pub struct SsrParams { pub parse_only: bool, } +pub enum StatusNotification {} + +#[serde(rename_all = "camelCase")] +#[derive(Serialize, Deserialize)] +pub enum Status { + Loading, + Ready, + NeedsReload, + Invalid, +} + +impl Notification for StatusNotification { + type Params = Status; + const METHOD: &'static str = "rust-analyzer/status"; +} + pub enum CodeActionRequest {} impl Request for CodeActionRequest { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index e03038b25..a5a8c17a0 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -169,16 +169,16 @@ impl GlobalState { } vfs::loader::Message::Progress { n_total, n_done } => { if n_total == 0 { - self.status = Status::Ready; + self.transition(Status::Invalid); } else { let state = if n_done == 0 { - self.status = Status::Loading; + self.transition(Status::Loading); Progress::Begin } else if n_done < n_total { Progress::Report } else { assert_eq!(n_done, n_total); - self.status = Status::Ready; + self.transition(Status::Ready); Progress::End }; self.report_progress( @@ -274,6 +274,18 @@ impl GlobalState { Ok(()) } + fn transition(&mut self, new_status: Status) { + self.status = Status::Ready; + if self.config.client_caps.status_notification { + let lsp_status = match new_status { + Status::Loading => lsp_ext::Status::Loading, + Status::Ready => lsp_ext::Status::Ready, + Status::Invalid => lsp_ext::Status::Invalid, + }; + self.send_notification::(lsp_status); + } + } + fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> { self.register_request(&req, request_received); diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index c0afb16d3..6d6bbac7c 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -399,6 +399,18 @@ Returns internal status message, mostly for debugging purposes. Reloads project information (that is, re-executes `cargo metadata`). +## Status Notification + +**Client Capability:** `{ "statusNotification": boolean }` + +**Method:** `rust-analyzer/status` + +**Notification:** `"loading" | "ready" | "invalid" | "needsReload"` + +This notification is sent from server to client. +The client can use it to display persistent status to the user (in modline). +For `needsReload` state, the client can provide a context-menu action to run `rust-analyzer/reloadWorkspace` request. + ## Syntax Tree **Method:** `rust-analyzer/syntaxTree` diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 65ad573d8..3e5915c28 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -161,6 +161,7 @@ class ExperimentalFeatures implements lc.StaticFeature { caps.codeActionGroup = true; caps.resolveCodeAction = true; caps.hoverActions = true; + caps.statusNotification = true; capabilities.experimental = caps; } initialize(_capabilities: lc.ServerCapabilities, _documentSelector: lc.DocumentSelector | undefined): void { diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 41df11991..6e767babf 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -1,9 +1,11 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; +import * as ra from './lsp_ext'; import { Config } from './config'; import { createClient } from './client'; import { isRustEditor, RustEditor } from './util'; +import { Status } from './lsp_ext'; export class Ctx { private constructor( @@ -11,6 +13,7 @@ export class Ctx { private readonly extCtx: vscode.ExtensionContext, readonly client: lc.LanguageClient, readonly serverPath: string, + readonly statusBar: vscode.StatusBarItem, ) { } @@ -22,9 +25,18 @@ export class Ctx { cwd: string, ): Promise { const client = createClient(serverPath, cwd); - const res = new Ctx(config, extCtx, client, serverPath); + + const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + extCtx.subscriptions.push(statusBar); + statusBar.text = "rust-analyzer"; + statusBar.tooltip = "ready"; + statusBar.show(); + + const res = new Ctx(config, extCtx, client, serverPath, statusBar); + res.pushCleanup(client.start()); await client.onReady(); + client.onNotification(ra.status, (status) => res.setStatus(status)); return res; } @@ -54,6 +66,35 @@ export class Ctx { return this.extCtx.subscriptions; } + setStatus(status: Status) { + switch (status) { + case "loading": + this.statusBar.text = "$(sync~spin) rust-analyzer"; + this.statusBar.tooltip = "Loading the project"; + this.statusBar.command = undefined; + this.statusBar.color = undefined; + break; + case "ready": + this.statusBar.text = "rust-analyzer"; + this.statusBar.tooltip = "Ready"; + this.statusBar.command = undefined; + this.statusBar.color = undefined; + break; + case "invalid": + this.statusBar.text = "$(error) rust-analyzer"; + this.statusBar.tooltip = "Failed to load the project"; + this.statusBar.command = undefined; + this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground"); + break; + case "needsReload": + this.statusBar.text = "$(warning) rust-analyzer"; + this.statusBar.tooltip = "Click to reload"; + this.statusBar.command = "rust-analyzer.reloadWorkspace"; + this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground"); + break; + } + } + pushCleanup(d: Disposable) { this.extCtx.subscriptions.push(d); } diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 981b6f40e..bf4703239 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -6,6 +6,9 @@ import * as lc from "vscode-languageclient"; export const analyzerStatus = new lc.RequestType("rust-analyzer/analyzerStatus"); +export type Status = "loading" | "ready" | "invalid" | "needsReload"; +export const status = new lc.NotificationType("rust-analyzer/status"); + export const reloadWorkspace = new lc.RequestType("rust-analyzer/reloadWorkspace"); export interface SyntaxTreeParams { -- cgit v1.2.3