More robust status notifications

This commit is contained in:
Aleksey Kladov 2021-04-06 14:16:35 +03:00
parent 9143e3925c
commit 8fe20b19d4
11 changed files with 169 additions and 154 deletions

View file

@ -445,8 +445,8 @@ pub fn code_action_group(&self) -> bool {
pub fn hover_actions(&self) -> bool { pub fn hover_actions(&self) -> bool {
self.experimental("hoverActions") self.experimental("hoverActions")
} }
pub fn status_notification(&self) -> bool { pub fn server_status_notification(&self) -> bool {
self.experimental("statusNotification") self.experimental("serverStatusNotification")
} }
pub fn publish_diagnostics(&self) -> bool { pub fn publish_diagnostics(&self) -> bool {

View file

@ -23,6 +23,7 @@
document::DocumentData, document::DocumentData,
from_proto, from_proto,
line_index::{LineEndings, LineIndex}, line_index::{LineEndings, LineIndex},
lsp_ext,
main_loop::Task, main_loop::Task,
op_queue::OpQueue, op_queue::OpQueue,
reload::SourceRootConfig, reload::SourceRootConfig,
@ -32,20 +33,6 @@
Result, Result,
}; };
#[derive(Eq, PartialEq, Copy, Clone)]
pub(crate) enum Status {
Loading,
Ready { partial: bool },
Invalid,
NeedsReload,
}
impl Default for Status {
fn default() -> Self {
Status::Loading
}
}
// Enforces drop order // Enforces drop order
pub(crate) struct Handle<H, C> { pub(crate) struct Handle<H, C> {
pub(crate) handle: H, pub(crate) handle: H,
@ -73,7 +60,7 @@ pub(crate) struct GlobalState {
pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>, pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
pub(crate) shutdown_requested: bool, pub(crate) shutdown_requested: bool,
pub(crate) status: Status, pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
pub(crate) source_root_config: SourceRootConfig, pub(crate) source_root_config: SourceRootConfig,
pub(crate) proc_macro_client: Option<ProcMacroClient>, pub(crate) proc_macro_client: Option<ProcMacroClient>,
@ -83,6 +70,7 @@ pub(crate) struct GlobalState {
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
pub(crate) vfs_config_version: u32, pub(crate) vfs_config_version: u32,
pub(crate) vfs_progress_config_version: u32,
pub(crate) vfs_progress_n_total: usize, pub(crate) vfs_progress_n_total: usize,
pub(crate) vfs_progress_n_done: usize, pub(crate) vfs_progress_n_done: usize,
@ -141,7 +129,7 @@ pub(crate) fn new(sender: Sender<lsp_server::Message>, config: Config) -> Global
mem_docs: FxHashMap::default(), mem_docs: FxHashMap::default(),
semantic_tokens_cache: Arc::new(Default::default()), semantic_tokens_cache: Arc::new(Default::default()),
shutdown_requested: false, shutdown_requested: false,
status: Status::default(), last_reported_status: None,
source_root_config: SourceRootConfig::default(), source_root_config: SourceRootConfig::default(),
proc_macro_client: None, proc_macro_client: None,
@ -151,14 +139,15 @@ pub(crate) fn new(sender: Sender<lsp_server::Message>, config: Config) -> Global
vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
vfs_config_version: 0, vfs_config_version: 0,
vfs_progress_config_version: 0,
vfs_progress_n_total: 0, vfs_progress_n_total: 0,
vfs_progress_n_done: 0, vfs_progress_n_done: 0,
workspaces: Arc::new(Vec::new()), workspaces: Arc::new(Vec::new()),
fetch_workspaces_queue: OpQueue::default(), fetch_workspaces_queue: OpQueue::default(),
workspace_build_data: None, workspace_build_data: None,
fetch_build_data_queue: OpQueue::default(),
fetch_build_data_queue: OpQueue::default(),
latest_requests: Default::default(), latest_requests: Default::default(),
} }
} }

View file

@ -241,26 +241,26 @@ pub struct SsrParams {
pub selections: Vec<lsp_types::Range>, pub selections: Vec<lsp_types::Range>,
} }
pub enum StatusNotification {} pub enum ServerStatusNotification {}
#[derive(Serialize, Deserialize)] impl Notification for ServerStatusNotification {
type Params = ServerStatusParams;
const METHOD: &'static str = "experimental/serverStatus";
}
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct ServerStatusParams {
pub health: Health,
pub quiescent: bool,
pub message: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum Status { pub enum Health {
Loading, Ok,
ReadyPartial, Warning,
Ready, Error,
NeedsReload,
Invalid,
}
#[derive(Deserialize, Serialize)]
pub struct StatusParams {
pub status: Status,
}
impl Notification for StatusNotification {
type Params = StatusParams;
const METHOD: &'static str = "rust-analyzer/status";
} }
pub enum CodeActionRequest {} pub enum CodeActionRequest {}

View file

@ -21,7 +21,7 @@
dispatch::{NotificationDispatcher, RequestDispatcher}, dispatch::{NotificationDispatcher, RequestDispatcher},
document::DocumentData, document::DocumentData,
from_proto, from_proto,
global_state::{file_id_to_url, url_to_file_id, GlobalState, Status}, global_state::{file_id_to_url, url_to_file_id, GlobalState},
handlers, lsp_ext, handlers, lsp_ext,
lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress}, lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress},
reload::{BuildDataProgress, ProjectWorkspaceProgress}, reload::{BuildDataProgress, ProjectWorkspaceProgress},
@ -189,7 +189,7 @@ fn handle_event(&mut self, event: Event) -> Result<()> {
log::info!("task queue len: {}", task_queue_len); log::info!("task queue len: {}", task_queue_len);
} }
let mut new_status = self.status; let was_quiescent = self.is_quiescent();
match event { match event {
Event::Lsp(msg) => match msg { Event::Lsp(msg) => match msg {
lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, lsp_server::Message::Request(req) => self.on_request(loop_start, req)?,
@ -314,9 +314,12 @@ fn handle_event(&mut self, event: Event) -> Result<()> {
} }
} }
vfs::loader::Message::Progress { n_total, n_done, config_version } => { vfs::loader::Message::Progress { n_total, n_done, config_version } => {
always!(config_version <= self.vfs_config_version);
self.vfs_progress_config_version = config_version;
self.vfs_progress_n_total = n_total; self.vfs_progress_n_total = n_total;
self.vfs_progress_n_done = n_done; self.vfs_progress_n_done = n_done;
always!(config_version <= self.vfs_config_version);
let state = if n_done == 0 { let state = if n_done == 0 {
Progress::Begin Progress::Begin
} else if n_done < n_total { } else if n_done < n_total {
@ -406,18 +409,14 @@ fn handle_event(&mut self, event: Event) -> Result<()> {
} }
let state_changed = self.process_changes(); let state_changed = self.process_changes();
let prev_status = self.status;
if prev_status != new_status { if self.is_quiescent() && !was_quiescent {
self.transition(new_status);
}
let is_ready = matches!(self.status, Status::Ready { .. });
if prev_status == Status::Loading && is_ready {
for flycheck in &self.flycheck { for flycheck in &self.flycheck {
flycheck.update(); flycheck.update();
} }
} }
if is_ready && (state_changed || prev_status == Status::Loading) { if self.is_quiescent() && (!was_quiescent || state_changed) {
self.update_file_notifications_on_threadpool(); self.update_file_notifications_on_threadpool();
// Refresh semantic tokens if the client supports it. // Refresh semantic tokens if the client supports it.
@ -451,6 +450,8 @@ fn handle_event(&mut self, event: Event) -> Result<()> {
} }
self.fetch_build_data_if_needed(); self.fetch_build_data_if_needed();
self.report_new_status_if_needed();
let loop_duration = loop_start.elapsed(); let loop_duration = loop_start.elapsed();
if loop_duration > Duration::from_millis(100) { if loop_duration > Duration::from_millis(100) {
log::warn!("overly long loop turn: {:?}", loop_duration); log::warn!("overly long loop turn: {:?}", loop_duration);
@ -477,7 +478,8 @@ fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()>
return Ok(()); return Ok(());
} }
if self.status == Status::Loading && req.method != "shutdown" { // Avoid flashing a bunch of unresolved references during initial load.
if self.workspaces.is_empty() && !self.is_quiescent() {
self.respond(lsp_server::Response::new_err( self.respond(lsp_server::Response::new_err(
req.id, req.id,
// FIXME: i32 should impl From<ErrorCode> (from() guarantees lossless conversion) // FIXME: i32 should impl From<ErrorCode> (from() guarantees lossless conversion)

View file

@ -2,27 +2,27 @@
//! at a time. //! at a time.
pub(crate) struct OpQueue<Args, Output> { pub(crate) struct OpQueue<Args, Output> {
op_scheduled: Option<Args>, op_requested: Option<Args>,
op_in_progress: bool, op_in_progress: bool,
last_op_result: Output, last_op_result: Output,
} }
impl<Args, Output: Default> Default for OpQueue<Args, Output> { impl<Args, Output: Default> Default for OpQueue<Args, Output> {
fn default() -> Self { fn default() -> Self {
Self { op_scheduled: None, op_in_progress: false, last_op_result: Default::default() } Self { op_requested: None, op_in_progress: false, last_op_result: Default::default() }
} }
} }
impl<Args, Output> OpQueue<Args, Output> { impl<Args, Output> OpQueue<Args, Output> {
pub(crate) fn request_op(&mut self, data: Args) { pub(crate) fn request_op(&mut self, data: Args) {
self.op_scheduled = Some(data); self.op_requested = Some(data);
} }
pub(crate) fn should_start_op(&mut self) -> Option<Args> { pub(crate) fn should_start_op(&mut self) -> Option<Args> {
if self.op_in_progress { if self.op_in_progress {
return None; return None;
} }
self.op_in_progress = self.op_scheduled.is_some(); self.op_in_progress = self.op_requested.is_some();
self.op_scheduled.take() self.op_requested.take()
} }
pub(crate) fn op_completed(&mut self, result: Output) { pub(crate) fn op_completed(&mut self, result: Output) {
assert!(self.op_in_progress); assert!(self.op_in_progress);
@ -34,4 +34,10 @@ pub(crate) fn op_completed(&mut self, result: Output) {
pub(crate) fn last_op_result(&self) -> &Output { pub(crate) fn last_op_result(&self) -> &Output {
&self.last_op_result &self.last_op_result
} }
pub(crate) fn op_in_progress(&self) -> bool {
self.op_in_progress
}
pub(crate) fn op_requested(&self) -> bool {
self.op_requested.is_some()
}
} }

View file

@ -9,11 +9,10 @@
use crate::{ use crate::{
config::{Config, FilesWatcher, LinkedProject}, config::{Config, FilesWatcher, LinkedProject},
global_state::{GlobalState, Status}, global_state::GlobalState,
lsp_ext, lsp_ext,
main_loop::Task, main_loop::Task,
}; };
use lsp_ext::StatusParams;
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum ProjectWorkspaceProgress { pub(crate) enum ProjectWorkspaceProgress {
@ -30,6 +29,13 @@ pub(crate) enum BuildDataProgress {
} }
impl GlobalState { impl GlobalState {
pub(crate) fn is_quiescent(&self) -> bool {
!(self.fetch_workspaces_queue.op_in_progress()
|| self.fetch_build_data_queue.op_in_progress()
|| self.vfs_progress_config_version < self.vfs_config_version
|| self.vfs_progress_n_done < self.vfs_progress_n_total)
}
pub(crate) fn update_configuration(&mut self, config: Config) { pub(crate) fn update_configuration(&mut self, config: Config) {
let _p = profile::span("GlobalState::update_configuration"); let _p = profile::span("GlobalState::update_configuration");
let old_config = mem::replace(&mut self.config, Arc::new(config)); let old_config = mem::replace(&mut self.config, Arc::new(config));
@ -46,17 +52,13 @@ pub(crate) fn maybe_refresh(&mut self, changes: &[(AbsPathBuf, ChangeKind)]) {
if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) { if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
return; return;
} }
match self.status {
Status::Loading | Status::NeedsReload => return,
Status::Ready { .. } | Status::Invalid => (),
}
log::info!( log::info!(
"Reloading workspace because of the following changes: {}", "Requesting workspace reload because of the following changes: {}",
itertools::join( itertools::join(
changes changes
.iter() .iter()
.filter(|(path, kind)| is_interesting(path, *kind)) .filter(|(path, kind)| is_interesting(path, *kind))
.map(|(path, kind)| format!("{}/{:?}", path.display(), kind)), .map(|(path, kind)| format!("{}: {:?}", path.display(), kind)),
", " ", "
) )
); );
@ -97,19 +99,31 @@ fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool {
false false
} }
} }
pub(crate) fn transition(&mut self, new_status: Status) { pub(crate) fn report_new_status_if_needed(&mut self) {
self.status = new_status; if !self.config.server_status_notification() {
if self.config.status_notification() { return;
let lsp_status = match new_status { }
Status::Loading => lsp_ext::Status::Loading,
Status::Ready { partial: true } => lsp_ext::Status::ReadyPartial, let mut status = lsp_ext::ServerStatusParams {
Status::Ready { partial: false } => lsp_ext::Status::Ready, health: lsp_ext::Health::Ok,
Status::Invalid => lsp_ext::Status::Invalid, quiescent: self.is_quiescent(),
Status::NeedsReload => lsp_ext::Status::NeedsReload, message: None,
}; };
self.send_notification::<lsp_ext::StatusNotification>(StatusParams { if !self.config.cargo_autoreload()
status: lsp_status, && self.is_quiescent()
}); && self.fetch_workspaces_queue.op_requested()
{
status.health = lsp_ext::Health::Warning;
status.message = Some("Workspace reload required".to_string())
}
if let Some(error) = self.loading_error() {
status.health = lsp_ext::Health::Error;
status.message = Some(format!("Workspace reload failed: {}", error))
}
if self.last_reported_status.as_ref() != Some(&status) {
self.last_reported_status = Some(status.clone());
self.send_notification::<lsp_ext::ServerStatusNotification>(status);
} }
} }
@ -201,45 +215,28 @@ pub(crate) fn fetch_build_data_completed(
pub(crate) fn switch_workspaces(&mut self) { pub(crate) fn switch_workspaces(&mut self) {
let _p = profile::span("GlobalState::switch_workspaces"); let _p = profile::span("GlobalState::switch_workspaces");
let workspaces = self.fetch_workspaces_queue.last_op_result(); log::info!("will switch workspaces");
log::info!("will switch workspaces: {:?}", workspaces);
let mut error_message = None; if let Some(error_message) = self.loading_error() {
let workspaces = workspaces log::error!("failed to switch workspaces: {}", error_message);
.iter()
.filter_map(|res| match res {
Ok(it) => Some(it.clone()),
Err(err) => {
log::error!("failed to load workspace: {:#}", err);
let message = error_message.get_or_insert_with(String::new);
stdx::format_to!(
message,
"rust-analyzer failed to load workspace: {:#}\n",
err
);
None
}
})
.collect::<Vec<_>>();
let workspace_build_data = match self.fetch_build_data_queue.last_op_result() {
Some(Ok(it)) => Some(it.clone()),
None => None,
Some(Err(err)) => {
log::error!("failed to fetch build data: {:#}", err);
let message = error_message.get_or_insert_with(String::new);
stdx::format_to!(message, "rust-analyzer failed to fetch build data: {:#}\n", err);
None
}
};
if let Some(error_message) = error_message {
self.show_message(lsp_types::MessageType::Error, error_message); self.show_message(lsp_types::MessageType::Error, error_message);
if !self.workspaces.is_empty() { if !self.workspaces.is_empty() {
return; return;
} }
} }
let workspaces = self
.fetch_workspaces_queue
.last_op_result()
.iter()
.filter_map(|res| res.as_ref().ok().cloned())
.collect::<Vec<_>>();
let workspace_build_data = match self.fetch_build_data_queue.last_op_result() {
Some(Ok(it)) => Some(it.clone()),
None | Some(Err(_)) => None,
};
if *self.workspaces == workspaces && self.workspace_build_data == workspace_build_data { if *self.workspaces == workspaces && self.workspace_build_data == workspace_build_data {
return; return;
} }
@ -346,6 +343,24 @@ pub(crate) fn switch_workspaces(&mut self) {
log::info!("did switch workspaces"); log::info!("did switch workspaces");
} }
fn loading_error(&self) -> Option<String> {
let mut message = None;
for ws in self.fetch_workspaces_queue.last_op_result() {
if let Err(err) = ws {
let message = message.get_or_insert_with(String::new);
stdx::format_to!(message, "rust-analyzer failed to load workspace: {:#}\n", err);
}
}
if let Some(Err(err)) = self.fetch_build_data_queue.last_op_result() {
let message = message.get_or_insert_with(String::new);
stdx::format_to!(message, "rust-analyzer failed to fetch build data: {:#}\n", err);
}
message
}
fn reload_flycheck(&mut self) { fn reload_flycheck(&mut self) {
let _p = profile::span("GlobalState::reload_flycheck"); let _p = profile::span("GlobalState::reload_flycheck");
let config = match self.config.flycheck() { let config = match self.config.flycheck() {

View file

@ -103,7 +103,7 @@ pub(crate) fn server(self) -> Server {
..Default::default() ..Default::default()
}), }),
experimental: Some(json!({ experimental: Some(json!({
"statusNotification": true, "serverStatusNotification": true,
})), })),
..Default::default() ..Default::default()
}, },
@ -213,13 +213,12 @@ fn send_request_(&self, r: Request) -> Value {
} }
pub(crate) fn wait_until_workspace_is_loaded(self) -> Server { pub(crate) fn wait_until_workspace_is_loaded(self) -> Server {
self.wait_for_message_cond(1, &|msg: &Message| match msg { self.wait_for_message_cond(1, &|msg: &Message| match msg {
Message::Notification(n) if n.method == "rust-analyzer/status" => { Message::Notification(n) if n.method == "experimental/serverStatus" => {
let status = n let status = n
.clone() .clone()
.extract::<lsp_ext::StatusParams>("rust-analyzer/status") .extract::<lsp_ext::ServerStatusParams>("experimental/serverStatus")
.unwrap() .unwrap();
.status; status.quiescent
matches!(status, lsp_ext::Status::Ready)
} }
_ => false, _ => false,
}) })

View file

@ -1,5 +1,5 @@
<!--- <!---
lsp_ext.rs hash: e8a7502bd2b2c2f5 lsp_ext.rs hash: faae991334a151d0
If you need to change the above hash to make the test pass, please check if you If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue: need to adjust this doc as well and ping this issue:
@ -419,23 +419,37 @@ Returns internal status message, mostly for debugging purposes.
Reloads project information (that is, re-executes `cargo metadata`). Reloads project information (that is, re-executes `cargo metadata`).
## Status Notification ## Server Status
**Experimental Client Capability:** `{ "statusNotification": boolean }` **Experimental Client Capability:** `{ "serverStatus": boolean }`
**Method:** `rust-analyzer/status` **Method:** `experimental/serverStatus`
**Notification:** **Notification:**
```typescript ```typescript
interface StatusParams { interface ServerStatusParams {
status: "loading" | "readyPartial" | "ready" | "invalid" | "needsReload", /// `ok` means that the server is completely functional.
///
/// `warning` means that the server is partially functional.
/// It can server requests, but some results might be wrong due to,
/// for example, some missing dependencies.
///
/// `error` means that the server is not functional. For example,
/// there's a fatal build configuration problem.
health: "ok" | "warning" | "error",
/// Is there any pending background work which might change the status?
/// For example, are dependencies being downloaded?
quiescent: bool,
/// Explanatory message to show on hover.
message?: string,
} }
``` ```
This notification is sent from server to client. This notification is sent from server to client.
The client can use it to display persistent status to the user (in modline). 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. It is similar to the `showMessage`, but is intended for stares rather than point-in-time events.
## Syntax Tree ## Syntax Tree

View file

@ -159,7 +159,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
caps.snippetTextEdit = true; caps.snippetTextEdit = true;
caps.codeActionGroup = true; caps.codeActionGroup = true;
caps.hoverActions = true; caps.hoverActions = true;
caps.statusNotification = true; caps.serverStatusNotification = true;
capabilities.experimental = caps; capabilities.experimental = caps;
} }
initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {

View file

@ -5,7 +5,7 @@ import * as ra from './lsp_ext';
import { Config } from './config'; import { Config } from './config';
import { createClient } from './client'; import { createClient } from './client';
import { isRustEditor, RustEditor } from './util'; import { isRustEditor, RustEditor } from './util';
import { Status } from './lsp_ext'; import { ServerStatusParams } from './lsp_ext';
export class Ctx { export class Ctx {
private constructor( private constructor(
@ -36,7 +36,7 @@ export class Ctx {
res.pushCleanup(client.start()); res.pushCleanup(client.start());
await client.onReady(); await client.onReady();
client.onNotification(ra.status, (params) => res.setStatus(params.status)); client.onNotification(ra.serverStatus, (params) => res.setServerStatus(params));
return res; return res;
} }
@ -66,39 +66,28 @@ export class Ctx {
return this.extCtx.subscriptions; return this.extCtx.subscriptions;
} }
setStatus(status: Status) { setServerStatus(status: ServerStatusParams) {
switch (status) { this.statusBar.tooltip = status.message ?? "Ready";
case "loading": let icon = "";
this.statusBar.text = "$(sync~spin) rust-analyzer"; switch (status.health) {
this.statusBar.tooltip = "Loading the project"; case "ok":
this.statusBar.command = undefined;
this.statusBar.color = undefined; this.statusBar.color = undefined;
break; break;
case "readyPartial": case "warning":
this.statusBar.text = "rust-analyzer"; this.statusBar.tooltip += "\nClick to reload."
this.statusBar.tooltip = "Ready (Partial)";
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.command = "rust-analyzer.reloadWorkspace";
this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground"); 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; break;
} }
if (!status.quiescent) icon = "$(sync~spin) ";
this.statusBar.text = `${icon} rust-analyzer`;
} }
pushCleanup(d: Disposable) { pushCleanup(d: Disposable) {

View file

@ -10,11 +10,12 @@ export interface AnalyzerStatusParams {
export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus"); export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus");
export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage"); export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage");
export type Status = "loading" | "ready" | "readyPartial" | "invalid" | "needsReload"; export interface ServerStatusParams {
export interface StatusParams { health: "ok" | "warning" | "error"
status: Status; quiescent: boolean
message?: string
} }
export const status = new lc.NotificationType<StatusParams>("rust-analyzer/status"); export const serverStatus = new lc.NotificationType<ServerStatusParams>("experimental/serverStatus");
export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace"); export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");