aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/rust-analyzer/src/config.rs4
-rw-r--r--crates/rust-analyzer/src/global_state.rs23
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs30
-rw-r--r--crates/rust-analyzer/src/main_loop.rs24
-rw-r--r--crates/rust-analyzer/src/op_queue.rs16
-rw-r--r--crates/rust-analyzer/src/reload.rs119
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/support.rs11
-rw-r--r--docs/dev/lsp-extensions.md30
-rw-r--r--editors/code/src/client.ts2
-rw-r--r--editors/code/src/ctx.ts47
-rw-r--r--editors/code/src/lsp_ext.ts9
11 files changed, 165 insertions, 150 deletions
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index cda272fd4..e012b4452 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -445,8 +445,8 @@ impl Config {
445 pub fn hover_actions(&self) -> bool { 445 pub fn hover_actions(&self) -> bool {
446 self.experimental("hoverActions") 446 self.experimental("hoverActions")
447 } 447 }
448 pub fn status_notification(&self) -> bool { 448 pub fn server_status_notification(&self) -> bool {
449 self.experimental("statusNotification") 449 self.experimental("serverStatusNotification")
450 } 450 }
451 451
452 pub fn publish_diagnostics(&self) -> bool { 452 pub fn publish_diagnostics(&self) -> bool {
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 44a656e62..adeb7a97e 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -23,6 +23,7 @@ use crate::{
23 document::DocumentData, 23 document::DocumentData,
24 from_proto, 24 from_proto,
25 line_index::{LineEndings, LineIndex}, 25 line_index::{LineEndings, LineIndex},
26 lsp_ext,
26 main_loop::Task, 27 main_loop::Task,
27 op_queue::OpQueue, 28 op_queue::OpQueue,
28 reload::SourceRootConfig, 29 reload::SourceRootConfig,
@@ -32,20 +33,6 @@ use crate::{
32 Result, 33 Result,
33}; 34};
34 35
35#[derive(Eq, PartialEq, Copy, Clone)]
36pub(crate) enum Status {
37 Loading,
38 Ready { partial: bool },
39 Invalid,
40 NeedsReload,
41}
42
43impl Default for Status {
44 fn default() -> Self {
45 Status::Loading
46 }
47}
48
49// Enforces drop order 36// Enforces drop order
50pub(crate) struct Handle<H, C> { 37pub(crate) struct Handle<H, C> {
51 pub(crate) handle: H, 38 pub(crate) handle: H,
@@ -73,7 +60,7 @@ pub(crate) struct GlobalState {
73 pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>, 60 pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
74 pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, 61 pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
75 pub(crate) shutdown_requested: bool, 62 pub(crate) shutdown_requested: bool,
76 pub(crate) status: Status, 63 pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
77 pub(crate) source_root_config: SourceRootConfig, 64 pub(crate) source_root_config: SourceRootConfig,
78 pub(crate) proc_macro_client: Option<ProcMacroClient>, 65 pub(crate) proc_macro_client: Option<ProcMacroClient>,
79 66
@@ -83,6 +70,7 @@ pub(crate) struct GlobalState {
83 70
84 pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, 71 pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
85 pub(crate) vfs_config_version: u32, 72 pub(crate) vfs_config_version: u32,
73 pub(crate) vfs_progress_config_version: u32,
86 pub(crate) vfs_progress_n_total: usize, 74 pub(crate) vfs_progress_n_total: usize,
87 pub(crate) vfs_progress_n_done: usize, 75 pub(crate) vfs_progress_n_done: usize,
88 76
@@ -141,7 +129,7 @@ impl GlobalState {
141 mem_docs: FxHashMap::default(), 129 mem_docs: FxHashMap::default(),
142 semantic_tokens_cache: Arc::new(Default::default()), 130 semantic_tokens_cache: Arc::new(Default::default()),
143 shutdown_requested: false, 131 shutdown_requested: false,
144 status: Status::default(), 132 last_reported_status: None,
145 source_root_config: SourceRootConfig::default(), 133 source_root_config: SourceRootConfig::default(),
146 proc_macro_client: None, 134 proc_macro_client: None,
147 135
@@ -151,14 +139,15 @@ impl GlobalState {
151 139
152 vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), 140 vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
153 vfs_config_version: 0, 141 vfs_config_version: 0,
142 vfs_progress_config_version: 0,
154 vfs_progress_n_total: 0, 143 vfs_progress_n_total: 0,
155 vfs_progress_n_done: 0, 144 vfs_progress_n_done: 0,
156 145
157 workspaces: Arc::new(Vec::new()), 146 workspaces: Arc::new(Vec::new()),
158 fetch_workspaces_queue: OpQueue::default(), 147 fetch_workspaces_queue: OpQueue::default(),
159 workspace_build_data: None, 148 workspace_build_data: None,
160 fetch_build_data_queue: OpQueue::default(),
161 149
150 fetch_build_data_queue: OpQueue::default(),
162 latest_requests: Default::default(), 151 latest_requests: Default::default(),
163 } 152 }
164 } 153 }
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 0e1fec209..81a6f22f1 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -241,26 +241,26 @@ pub struct SsrParams {
241 pub selections: Vec<lsp_types::Range>, 241 pub selections: Vec<lsp_types::Range>,
242} 242}
243 243
244pub enum StatusNotification {} 244pub enum ServerStatusNotification {}
245 245
246#[derive(Serialize, Deserialize)] 246impl Notification for ServerStatusNotification {
247#[serde(rename_all = "camelCase")] 247 type Params = ServerStatusParams;
248pub enum Status { 248 const METHOD: &'static str = "experimental/serverStatus";
249 Loading,
250 ReadyPartial,
251 Ready,
252 NeedsReload,
253 Invalid,
254} 249}
255 250
256#[derive(Deserialize, Serialize)] 251#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)]
257pub struct StatusParams { 252pub struct ServerStatusParams {
258 pub status: Status, 253 pub health: Health,
254 pub quiescent: bool,
255 pub message: Option<String>,
259} 256}
260 257
261impl Notification for StatusNotification { 258#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
262 type Params = StatusParams; 259#[serde(rename_all = "camelCase")]
263 const METHOD: &'static str = "rust-analyzer/status"; 260pub enum Health {
261 Ok,
262 Warning,
263 Error,
264} 264}
265 265
266pub enum CodeActionRequest {} 266pub enum CodeActionRequest {}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 4a4705e1f..a5655116b 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -21,7 +21,7 @@ use crate::{
21 dispatch::{NotificationDispatcher, RequestDispatcher}, 21 dispatch::{NotificationDispatcher, RequestDispatcher},
22 document::DocumentData, 22 document::DocumentData,
23 from_proto, 23 from_proto,
24 global_state::{file_id_to_url, url_to_file_id, GlobalState, Status}, 24 global_state::{file_id_to_url, url_to_file_id, GlobalState},
25 handlers, lsp_ext, 25 handlers, lsp_ext,
26 lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress}, 26 lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress},
27 reload::{BuildDataProgress, ProjectWorkspaceProgress}, 27 reload::{BuildDataProgress, ProjectWorkspaceProgress},
@@ -189,7 +189,7 @@ impl GlobalState {
189 log::info!("task queue len: {}", task_queue_len); 189 log::info!("task queue len: {}", task_queue_len);
190 } 190 }
191 191
192 let mut new_status = self.status; 192 let was_quiescent = self.is_quiescent();
193 match event { 193 match event {
194 Event::Lsp(msg) => match msg { 194 Event::Lsp(msg) => match msg {
195 lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, 195 lsp_server::Message::Request(req) => self.on_request(loop_start, req)?,
@@ -314,9 +314,12 @@ impl GlobalState {
314 } 314 }
315 } 315 }
316 vfs::loader::Message::Progress { n_total, n_done, config_version } => { 316 vfs::loader::Message::Progress { n_total, n_done, config_version } => {
317 always!(config_version <= self.vfs_config_version);
318
319 self.vfs_progress_config_version = config_version;
317 self.vfs_progress_n_total = n_total; 320 self.vfs_progress_n_total = n_total;
318 self.vfs_progress_n_done = n_done; 321 self.vfs_progress_n_done = n_done;
319 always!(config_version <= self.vfs_config_version); 322
320 let state = if n_done == 0 { 323 let state = if n_done == 0 {
321 Progress::Begin 324 Progress::Begin
322 } else if n_done < n_total { 325 } else if n_done < n_total {
@@ -406,18 +409,14 @@ impl GlobalState {
406 } 409 }
407 410
408 let state_changed = self.process_changes(); 411 let state_changed = self.process_changes();
409 let prev_status = self.status; 412
410 if prev_status != new_status { 413 if self.is_quiescent() && !was_quiescent {
411 self.transition(new_status);
412 }
413 let is_ready = matches!(self.status, Status::Ready { .. });
414 if prev_status == Status::Loading && is_ready {
415 for flycheck in &self.flycheck { 414 for flycheck in &self.flycheck {
416 flycheck.update(); 415 flycheck.update();
417 } 416 }
418 } 417 }
419 418
420 if is_ready && (state_changed || prev_status == Status::Loading) { 419 if self.is_quiescent() && (!was_quiescent || state_changed) {
421 self.update_file_notifications_on_threadpool(); 420 self.update_file_notifications_on_threadpool();
422 421
423 // Refresh semantic tokens if the client supports it. 422 // Refresh semantic tokens if the client supports it.
@@ -451,6 +450,8 @@ impl GlobalState {
451 } 450 }
452 self.fetch_build_data_if_needed(); 451 self.fetch_build_data_if_needed();
453 452
453 self.report_new_status_if_needed();
454
454 let loop_duration = loop_start.elapsed(); 455 let loop_duration = loop_start.elapsed();
455 if loop_duration > Duration::from_millis(100) { 456 if loop_duration > Duration::from_millis(100) {
456 log::warn!("overly long loop turn: {:?}", loop_duration); 457 log::warn!("overly long loop turn: {:?}", loop_duration);
@@ -477,7 +478,8 @@ impl GlobalState {
477 return Ok(()); 478 return Ok(());
478 } 479 }
479 480
480 if self.status == Status::Loading && req.method != "shutdown" { 481 // Avoid flashing a bunch of unresolved references during initial load.
482 if self.workspaces.is_empty() && !self.is_quiescent() {
481 self.respond(lsp_server::Response::new_err( 483 self.respond(lsp_server::Response::new_err(
482 req.id, 484 req.id,
483 // FIXME: i32 should impl From<ErrorCode> (from() guarantees lossless conversion) 485 // FIXME: i32 should impl From<ErrorCode> (from() guarantees lossless conversion)
diff --git a/crates/rust-analyzer/src/op_queue.rs b/crates/rust-analyzer/src/op_queue.rs
index f71b718bc..1d612a933 100644
--- a/crates/rust-analyzer/src/op_queue.rs
+++ b/crates/rust-analyzer/src/op_queue.rs
@@ -2,27 +2,27 @@
2//! at a time. 2//! at a time.
3 3
4pub(crate) struct OpQueue<Args, Output> { 4pub(crate) struct OpQueue<Args, Output> {
5 op_scheduled: Option<Args>, 5 op_requested: Option<Args>,
6 op_in_progress: bool, 6 op_in_progress: bool,
7 last_op_result: Output, 7 last_op_result: Output,
8} 8}
9 9
10impl<Args, Output: Default> Default for OpQueue<Args, Output> { 10impl<Args, Output: Default> Default for OpQueue<Args, Output> {
11 fn default() -> Self { 11 fn default() -> Self {
12 Self { op_scheduled: None, op_in_progress: false, last_op_result: Default::default() } 12 Self { op_requested: None, op_in_progress: false, last_op_result: Default::default() }
13 } 13 }
14} 14}
15 15
16impl<Args, Output> OpQueue<Args, Output> { 16impl<Args, Output> OpQueue<Args, Output> {
17 pub(crate) fn request_op(&mut self, data: Args) { 17 pub(crate) fn request_op(&mut self, data: Args) {
18 self.op_scheduled = Some(data); 18 self.op_requested = Some(data);
19 } 19 }
20 pub(crate) fn should_start_op(&mut self) -> Option<Args> { 20 pub(crate) fn should_start_op(&mut self) -> Option<Args> {
21 if self.op_in_progress { 21 if self.op_in_progress {
22 return None; 22 return None;
23 } 23 }
24 self.op_in_progress = self.op_scheduled.is_some(); 24 self.op_in_progress = self.op_requested.is_some();
25 self.op_scheduled.take() 25 self.op_requested.take()
26 } 26 }
27 pub(crate) fn op_completed(&mut self, result: Output) { 27 pub(crate) fn op_completed(&mut self, result: Output) {
28 assert!(self.op_in_progress); 28 assert!(self.op_in_progress);
@@ -34,4 +34,10 @@ impl<Args, Output> OpQueue<Args, Output> {
34 pub(crate) fn last_op_result(&self) -> &Output { 34 pub(crate) fn last_op_result(&self) -> &Output {
35 &self.last_op_result 35 &self.last_op_result
36 } 36 }
37 pub(crate) fn op_in_progress(&self) -> bool {
38 self.op_in_progress
39 }
40 pub(crate) fn op_requested(&self) -> bool {
41 self.op_requested.is_some()
42 }
37} 43}
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 5ff60c22a..301c7003b 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -9,11 +9,10 @@ use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
9 9
10use crate::{ 10use crate::{
11 config::{Config, FilesWatcher, LinkedProject}, 11 config::{Config, FilesWatcher, LinkedProject},
12 global_state::{GlobalState, Status}, 12 global_state::GlobalState,
13 lsp_ext, 13 lsp_ext,
14 main_loop::Task, 14 main_loop::Task,
15}; 15};
16use lsp_ext::StatusParams;
17 16
18#[derive(Debug)] 17#[derive(Debug)]
19pub(crate) enum ProjectWorkspaceProgress { 18pub(crate) enum ProjectWorkspaceProgress {
@@ -30,6 +29,13 @@ pub(crate) enum BuildDataProgress {
30} 29}
31 30
32impl GlobalState { 31impl GlobalState {
32 pub(crate) fn is_quiescent(&self) -> bool {
33 !(self.fetch_workspaces_queue.op_in_progress()
34 || self.fetch_build_data_queue.op_in_progress()
35 || self.vfs_progress_config_version < self.vfs_config_version
36 || self.vfs_progress_n_done < self.vfs_progress_n_total)
37 }
38
33 pub(crate) fn update_configuration(&mut self, config: Config) { 39 pub(crate) fn update_configuration(&mut self, config: Config) {
34 let _p = profile::span("GlobalState::update_configuration"); 40 let _p = profile::span("GlobalState::update_configuration");
35 let old_config = mem::replace(&mut self.config, Arc::new(config)); 41 let old_config = mem::replace(&mut self.config, Arc::new(config));
@@ -46,17 +52,13 @@ impl GlobalState {
46 if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) { 52 if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
47 return; 53 return;
48 } 54 }
49 match self.status {
50 Status::Loading | Status::NeedsReload => return,
51 Status::Ready { .. } | Status::Invalid => (),
52 }
53 log::info!( 55 log::info!(
54 "Reloading workspace because of the following changes: {}", 56 "Requesting workspace reload because of the following changes: {}",
55 itertools::join( 57 itertools::join(
56 changes 58 changes
57 .iter() 59 .iter()
58 .filter(|(path, kind)| is_interesting(path, *kind)) 60 .filter(|(path, kind)| is_interesting(path, *kind))
59 .map(|(path, kind)| format!("{}/{:?}", path.display(), kind)), 61 .map(|(path, kind)| format!("{}: {:?}", path.display(), kind)),
60 ", " 62 ", "
61 ) 63 )
62 ); 64 );
@@ -97,19 +99,31 @@ impl GlobalState {
97 false 99 false
98 } 100 }
99 } 101 }
100 pub(crate) fn transition(&mut self, new_status: Status) { 102 pub(crate) fn report_new_status_if_needed(&mut self) {
101 self.status = new_status; 103 if !self.config.server_status_notification() {
102 if self.config.status_notification() { 104 return;
103 let lsp_status = match new_status { 105 }
104 Status::Loading => lsp_ext::Status::Loading, 106
105 Status::Ready { partial: true } => lsp_ext::Status::ReadyPartial, 107 let mut status = lsp_ext::ServerStatusParams {
106 Status::Ready { partial: false } => lsp_ext::Status::Ready, 108 health: lsp_ext::Health::Ok,
107 Status::Invalid => lsp_ext::Status::Invalid, 109 quiescent: self.is_quiescent(),
108 Status::NeedsReload => lsp_ext::Status::NeedsReload, 110 message: None,
109 }; 111 };
110 self.send_notification::<lsp_ext::StatusNotification>(StatusParams { 112 if !self.config.cargo_autoreload()
111 status: lsp_status, 113 && self.is_quiescent()
112 }); 114 && self.fetch_workspaces_queue.op_requested()
115 {
116 status.health = lsp_ext::Health::Warning;
117 status.message = Some("Workspace reload required".to_string())
118 }
119 if let Some(error) = self.loading_error() {
120 status.health = lsp_ext::Health::Error;
121 status.message = Some(format!("Workspace reload failed: {}", error))
122 }
123
124 if self.last_reported_status.as_ref() != Some(&status) {
125 self.last_reported_status = Some(status.clone());
126 self.send_notification::<lsp_ext::ServerStatusNotification>(status);
113 } 127 }
114 } 128 }
115 129
@@ -201,45 +215,28 @@ impl GlobalState {
201 215
202 pub(crate) fn switch_workspaces(&mut self) { 216 pub(crate) fn switch_workspaces(&mut self) {
203 let _p = profile::span("GlobalState::switch_workspaces"); 217 let _p = profile::span("GlobalState::switch_workspaces");
204 let workspaces = self.fetch_workspaces_queue.last_op_result(); 218 log::info!("will switch workspaces");
205 log::info!("will switch workspaces: {:?}", workspaces); 219
220 if let Some(error_message) = self.loading_error() {
221 log::error!("failed to switch workspaces: {}", error_message);
222 self.show_message(lsp_types::MessageType::Error, error_message);
223 if !self.workspaces.is_empty() {
224 return;
225 }
226 }
206 227
207 let mut error_message = None; 228 let workspaces = self
208 let workspaces = workspaces 229 .fetch_workspaces_queue
230 .last_op_result()
209 .iter() 231 .iter()
210 .filter_map(|res| match res { 232 .filter_map(|res| res.as_ref().ok().cloned())
211 Ok(it) => Some(it.clone()),
212 Err(err) => {
213 log::error!("failed to load workspace: {:#}", err);
214 let message = error_message.get_or_insert_with(String::new);
215 stdx::format_to!(
216 message,
217 "rust-analyzer failed to load workspace: {:#}\n",
218 err
219 );
220 None
221 }
222 })
223 .collect::<Vec<_>>(); 233 .collect::<Vec<_>>();
224 234
225 let workspace_build_data = match self.fetch_build_data_queue.last_op_result() { 235 let workspace_build_data = match self.fetch_build_data_queue.last_op_result() {
226 Some(Ok(it)) => Some(it.clone()), 236 Some(Ok(it)) => Some(it.clone()),
227 None => None, 237 None | Some(Err(_)) => None,
228 Some(Err(err)) => {
229 log::error!("failed to fetch build data: {:#}", err);
230 let message = error_message.get_or_insert_with(String::new);
231 stdx::format_to!(message, "rust-analyzer failed to fetch build data: {:#}\n", err);
232 None
233 }
234 }; 238 };
235 239
236 if let Some(error_message) = error_message {
237 self.show_message(lsp_types::MessageType::Error, error_message);
238 if !self.workspaces.is_empty() {
239 return;
240 }
241 }
242
243 if *self.workspaces == workspaces && self.workspace_build_data == workspace_build_data { 240 if *self.workspaces == workspaces && self.workspace_build_data == workspace_build_data {
244 return; 241 return;
245 } 242 }
@@ -346,6 +343,24 @@ impl GlobalState {
346 log::info!("did switch workspaces"); 343 log::info!("did switch workspaces");
347 } 344 }
348 345
346 fn loading_error(&self) -> Option<String> {
347 let mut message = None;
348
349 for ws in self.fetch_workspaces_queue.last_op_result() {
350 if let Err(err) = ws {
351 let message = message.get_or_insert_with(String::new);
352 stdx::format_to!(message, "rust-analyzer failed to load workspace: {:#}\n", err);
353 }
354 }
355
356 if let Some(Err(err)) = self.fetch_build_data_queue.last_op_result() {
357 let message = message.get_or_insert_with(String::new);
358 stdx::format_to!(message, "rust-analyzer failed to fetch build data: {:#}\n", err);
359 }
360
361 message
362 }
363
349 fn reload_flycheck(&mut self) { 364 fn reload_flycheck(&mut self) {
350 let _p = profile::span("GlobalState::reload_flycheck"); 365 let _p = profile::span("GlobalState::reload_flycheck");
351 let config = match self.config.flycheck() { 366 let config = match self.config.flycheck() {
diff --git a/crates/rust-analyzer/tests/rust-analyzer/support.rs b/crates/rust-analyzer/tests/rust-analyzer/support.rs
index 95bf26f01..8d68f1b7d 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/support.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/support.rs
@@ -103,7 +103,7 @@ impl<'a> Project<'a> {
103 ..Default::default() 103 ..Default::default()
104 }), 104 }),
105 experimental: Some(json!({ 105 experimental: Some(json!({
106 "statusNotification": true, 106 "serverStatusNotification": true,
107 })), 107 })),
108 ..Default::default() 108 ..Default::default()
109 }, 109 },
@@ -213,13 +213,12 @@ impl Server {
213 } 213 }
214 pub(crate) fn wait_until_workspace_is_loaded(self) -> Server { 214 pub(crate) fn wait_until_workspace_is_loaded(self) -> Server {
215 self.wait_for_message_cond(1, &|msg: &Message| match msg { 215 self.wait_for_message_cond(1, &|msg: &Message| match msg {
216 Message::Notification(n) if n.method == "rust-analyzer/status" => { 216 Message::Notification(n) if n.method == "experimental/serverStatus" => {
217 let status = n 217 let status = n
218 .clone() 218 .clone()
219 .extract::<lsp_ext::StatusParams>("rust-analyzer/status") 219 .extract::<lsp_ext::ServerStatusParams>("experimental/serverStatus")
220 .unwrap() 220 .unwrap();
221 .status; 221 status.quiescent
222 matches!(status, lsp_ext::Status::Ready)
223 } 222 }
224 _ => false, 223 _ => false,
225 }) 224 })
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 73be59a82..989771ac6 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
1<!--- 1<!---
2lsp_ext.rs hash: e8a7502bd2b2c2f5 2lsp_ext.rs hash: faae991334a151d0
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need to adjust this doc as well and ping this issue:
@@ -419,23 +419,37 @@ Returns internal status message, mostly for debugging purposes.
419 419
420Reloads project information (that is, re-executes `cargo metadata`). 420Reloads project information (that is, re-executes `cargo metadata`).
421 421
422## Status Notification 422## Server Status
423 423
424**Experimental Client Capability:** `{ "statusNotification": boolean }` 424**Experimental Client Capability:** `{ "serverStatus": boolean }`
425 425
426**Method:** `rust-analyzer/status` 426**Method:** `experimental/serverStatus`
427 427
428**Notification:** 428**Notification:**
429 429
430```typescript 430```typescript
431interface StatusParams { 431interface ServerStatusParams {
432 status: "loading" | "readyPartial" | "ready" | "invalid" | "needsReload", 432 /// `ok` means that the server is completely functional.
433 ///
434 /// `warning` means that the server is partially functional.
435 /// It can server requests, but some results might be wrong due to,
436 /// for example, some missing dependencies.
437 ///
438 /// `error` means that the server is not functional. For example,
439 /// there's a fatal build configuration problem.
440 health: "ok" | "warning" | "error",
441 /// Is there any pending background work which might change the status?
442 /// For example, are dependencies being downloaded?
443 quiescent: bool,
444 /// Explanatory message to show on hover.
445 message?: string,
433} 446}
434``` 447```
435 448
436This notification is sent from server to client. 449This notification is sent from server to client.
437The client can use it to display persistent status to the user (in modline). 450The client can use it to display *persistent* status to the user (in modline).
438For `needsReload` state, the client can provide a context-menu action to run `rust-analyzer/reloadWorkspace` request. 451It is similar to the `showMessage`, but is intended for stares rather than point-in-time events.
452
439 453
440## Syntax Tree 454## Syntax Tree
441 455
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 0771ca3b6..116f41df6 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -159,7 +159,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
159 caps.snippetTextEdit = true; 159 caps.snippetTextEdit = true;
160 caps.codeActionGroup = true; 160 caps.codeActionGroup = true;
161 caps.hoverActions = true; 161 caps.hoverActions = true;
162 caps.statusNotification = true; 162 caps.serverStatusNotification = true;
163 capabilities.experimental = caps; 163 capabilities.experimental = caps;
164 } 164 }
165 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { 165 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index c07583cfa..c05e757f8 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -5,7 +5,7 @@ import * as ra from './lsp_ext';
5import { Config } from './config'; 5import { Config } from './config';
6import { createClient } from './client'; 6import { createClient } from './client';
7import { isRustEditor, RustEditor } from './util'; 7import { isRustEditor, RustEditor } from './util';
8import { Status } from './lsp_ext'; 8import { ServerStatusParams } from './lsp_ext';
9 9
10export class Ctx { 10export class Ctx {
11 private constructor( 11 private constructor(
@@ -36,7 +36,7 @@ export class Ctx {
36 36
37 res.pushCleanup(client.start()); 37 res.pushCleanup(client.start());
38 await client.onReady(); 38 await client.onReady();
39 client.onNotification(ra.status, (params) => res.setStatus(params.status)); 39 client.onNotification(ra.serverStatus, (params) => res.setServerStatus(params));
40 return res; 40 return res;
41 } 41 }
42 42
@@ -66,39 +66,28 @@ export class Ctx {
66 return this.extCtx.subscriptions; 66 return this.extCtx.subscriptions;
67 } 67 }
68 68
69 setStatus(status: Status) { 69 setServerStatus(status: ServerStatusParams) {
70 switch (status) { 70 this.statusBar.tooltip = status.message ?? "Ready";
71 case "loading": 71 let icon = "";
72 this.statusBar.text = "$(sync~spin) rust-analyzer"; 72 switch (status.health) {
73 this.statusBar.tooltip = "Loading the project"; 73 case "ok":
74 this.statusBar.command = undefined;
75 this.statusBar.color = undefined; 74 this.statusBar.color = undefined;
76 break; 75 break;
77 case "readyPartial": 76 case "warning":
78 this.statusBar.text = "rust-analyzer"; 77 this.statusBar.tooltip += "\nClick to reload."
79 this.statusBar.tooltip = "Ready (Partial)";
80 this.statusBar.command = undefined;
81 this.statusBar.color = undefined;
82 break;
83 case "ready":
84 this.statusBar.text = "rust-analyzer";
85 this.statusBar.tooltip = "Ready";
86 this.statusBar.command = undefined;
87 this.statusBar.color = undefined;
88 break;
89 case "invalid":
90 this.statusBar.text = "$(error) rust-analyzer";
91 this.statusBar.tooltip = "Failed to load the project";
92 this.statusBar.command = undefined;
93 this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground");
94 break;
95 case "needsReload":
96 this.statusBar.text = "$(warning) rust-analyzer";
97 this.statusBar.tooltip = "Click to reload";
98 this.statusBar.command = "rust-analyzer.reloadWorkspace"; 78 this.statusBar.command = "rust-analyzer.reloadWorkspace";
99 this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground"); 79 this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground");
80 icon = "$(warning) ";
81 break;
82 case "error":
83 this.statusBar.tooltip += "\nClick to reload."
84 this.statusBar.command = "rust-analyzer.reloadWorkspace";
85 this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground");
86 icon = "$(error) ";
100 break; 87 break;
101 } 88 }
89 if (!status.quiescent) icon = "$(sync~spin) ";
90 this.statusBar.text = `${icon} rust-analyzer`;
102 } 91 }
103 92
104 pushCleanup(d: Disposable) { 93 pushCleanup(d: Disposable) {
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 00e128b8c..2e1744f1b 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -10,11 +10,12 @@ export interface AnalyzerStatusParams {
10export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus"); 10export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus");
11export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage"); 11export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage");
12 12
13export type Status = "loading" | "ready" | "readyPartial" | "invalid" | "needsReload"; 13export interface ServerStatusParams {
14export interface StatusParams { 14 health: "ok" | "warning" | "error"
15 status: Status; 15 quiescent: boolean
16 message?: string
16} 17}
17export const status = new lc.NotificationType<StatusParams>("rust-analyzer/status"); 18export const serverStatus = new lc.NotificationType<ServerStatusParams>("experimental/serverStatus");
18 19
19export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace"); 20export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
20 21