diff options
author | Aleksey Kladov <[email protected]> | 2021-04-06 12:16:35 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-04-06 13:45:31 +0100 |
commit | 8fe20b19d4702fc6d6933c31abddc8539d2581f0 (patch) | |
tree | 8aca2a3aa2b059bb47cc235b1b557d4b4de93a24 | |
parent | 9143e3925cd95d30af72745f25e185f65a686d32 (diff) |
More robust status notifications
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 4 | ||||
-rw-r--r-- | crates/rust-analyzer/src/global_state.rs | 23 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lsp_ext.rs | 30 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 24 | ||||
-rw-r--r-- | crates/rust-analyzer/src/op_queue.rs | 16 | ||||
-rw-r--r-- | crates/rust-analyzer/src/reload.rs | 119 | ||||
-rw-r--r-- | crates/rust-analyzer/tests/rust-analyzer/support.rs | 11 | ||||
-rw-r--r-- | docs/dev/lsp-extensions.md | 30 | ||||
-rw-r--r-- | editors/code/src/client.ts | 2 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 47 | ||||
-rw-r--r-- | editors/code/src/lsp_ext.ts | 9 |
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)] | ||
36 | pub(crate) enum Status { | ||
37 | Loading, | ||
38 | Ready { partial: bool }, | ||
39 | Invalid, | ||
40 | NeedsReload, | ||
41 | } | ||
42 | |||
43 | impl Default for Status { | ||
44 | fn default() -> Self { | ||
45 | Status::Loading | ||
46 | } | ||
47 | } | ||
48 | |||
49 | // Enforces drop order | 36 | // Enforces drop order |
50 | pub(crate) struct Handle<H, C> { | 37 | pub(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 | ||
244 | pub enum StatusNotification {} | 244 | pub enum ServerStatusNotification {} |
245 | 245 | ||
246 | #[derive(Serialize, Deserialize)] | 246 | impl Notification for ServerStatusNotification { |
247 | #[serde(rename_all = "camelCase")] | 247 | type Params = ServerStatusParams; |
248 | pub 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)] |
257 | pub struct StatusParams { | 252 | pub struct ServerStatusParams { |
258 | pub status: Status, | 253 | pub health: Health, |
254 | pub quiescent: bool, | ||
255 | pub message: Option<String>, | ||
259 | } | 256 | } |
260 | 257 | ||
261 | impl 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"; | 260 | pub enum Health { |
261 | Ok, | ||
262 | Warning, | ||
263 | Error, | ||
264 | } | 264 | } |
265 | 265 | ||
266 | pub enum CodeActionRequest {} | 266 | pub 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 | ||
4 | pub(crate) struct OpQueue<Args, Output> { | 4 | pub(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 | ||
10 | impl<Args, Output: Default> Default for OpQueue<Args, Output> { | 10 | impl<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 | ||
16 | impl<Args, Output> OpQueue<Args, Output> { | 16 | impl<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 | ||
10 | use crate::{ | 10 | use 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 | }; |
16 | use lsp_ext::StatusParams; | ||
17 | 16 | ||
18 | #[derive(Debug)] | 17 | #[derive(Debug)] |
19 | pub(crate) enum ProjectWorkspaceProgress { | 18 | pub(crate) enum ProjectWorkspaceProgress { |
@@ -30,6 +29,13 @@ pub(crate) enum BuildDataProgress { | |||
30 | } | 29 | } |
31 | 30 | ||
32 | impl GlobalState { | 31 | impl 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 | <!--- |
2 | lsp_ext.rs hash: e8a7502bd2b2c2f5 | 2 | lsp_ext.rs hash: faae991334a151d0 |
3 | 3 | ||
4 | If you need to change the above hash to make the test pass, please check if you | 4 | If you need to change the above hash to make the test pass, please check if you |
5 | need to adjust this doc as well and ping this issue: | 5 | need to adjust this doc as well and ping this issue: |
@@ -419,23 +419,37 @@ Returns internal status message, mostly for debugging purposes. | |||
419 | 419 | ||
420 | Reloads project information (that is, re-executes `cargo metadata`). | 420 | Reloads 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 |
431 | interface StatusParams { | 431 | interface 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 | ||
436 | This notification is sent from server to client. | 449 | This notification is sent from server to client. |
437 | The client can use it to display persistent status to the user (in modline). | 450 | The client can use it to display *persistent* status to the user (in modline). |
438 | For `needsReload` state, the client can provide a context-menu action to run `rust-analyzer/reloadWorkspace` request. | 451 | It 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'; | |||
5 | import { Config } from './config'; | 5 | import { Config } from './config'; |
6 | import { createClient } from './client'; | 6 | import { createClient } from './client'; |
7 | import { isRustEditor, RustEditor } from './util'; | 7 | import { isRustEditor, RustEditor } from './util'; |
8 | import { Status } from './lsp_ext'; | 8 | import { ServerStatusParams } from './lsp_ext'; |
9 | 9 | ||
10 | export class Ctx { | 10 | export 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 { | |||
10 | export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus"); | 10 | export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus"); |
11 | export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage"); | 11 | export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage"); |
12 | 12 | ||
13 | export type Status = "loading" | "ready" | "readyPartial" | "invalid" | "needsReload"; | 13 | export interface ServerStatusParams { |
14 | export interface StatusParams { | 14 | health: "ok" | "warning" | "error" |
15 | status: Status; | 15 | quiescent: boolean |
16 | message?: string | ||
16 | } | 17 | } |
17 | export const status = new lc.NotificationType<StatusParams>("rust-analyzer/status"); | 18 | export const serverStatus = new lc.NotificationType<ServerStatusParams>("experimental/serverStatus"); |
18 | 19 | ||
19 | export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace"); | 20 | export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace"); |
20 | 21 | ||