diff options
-rw-r--r-- | crates/project_model/src/build_data.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 4 | ||||
-rw-r--r-- | crates/rust-analyzer/src/global_state.rs | 62 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lsp_ext.rs | 30 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 101 | ||||
-rw-r--r-- | crates/rust-analyzer/src/op_queue.rs | 16 | ||||
-rw-r--r-- | crates/rust-analyzer/src/reload.rs | 194 | ||||
-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 |
12 files changed, 272 insertions, 236 deletions
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs index f7050be4e..c2c87b207 100644 --- a/crates/project_model/src/build_data.rs +++ b/crates/project_model/src/build_data.rs | |||
@@ -52,7 +52,7 @@ pub struct BuildDataCollector { | |||
52 | configs: FxHashMap<AbsPathBuf, BuildDataConfig>, | 52 | configs: FxHashMap<AbsPathBuf, BuildDataConfig>, |
53 | } | 53 | } |
54 | 54 | ||
55 | #[derive(Debug, Default, PartialEq, Eq)] | 55 | #[derive(Debug, Default, PartialEq, Eq, Clone)] |
56 | pub struct BuildDataResult { | 56 | pub struct BuildDataResult { |
57 | data: FxHashMap<AbsPathBuf, BuildDataMap>, | 57 | data: FxHashMap<AbsPathBuf, BuildDataMap>, |
58 | } | 58 | } |
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 8679c8599..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, |
@@ -67,26 +54,36 @@ pub(crate) struct GlobalState { | |||
67 | req_queue: ReqQueue, | 54 | req_queue: ReqQueue, |
68 | pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>, | 55 | pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>, |
69 | pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>, | 56 | pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>, |
70 | pub(crate) vfs_config_version: u32, | ||
71 | pub(crate) flycheck: Vec<FlycheckHandle>, | ||
72 | pub(crate) flycheck_sender: Sender<flycheck::Message>, | ||
73 | pub(crate) flycheck_receiver: Receiver<flycheck::Message>, | ||
74 | pub(crate) config: Arc<Config>, | 57 | pub(crate) config: Arc<Config>, |
75 | pub(crate) analysis_host: AnalysisHost, | 58 | pub(crate) analysis_host: AnalysisHost, |
76 | pub(crate) diagnostics: DiagnosticCollection, | 59 | pub(crate) diagnostics: DiagnosticCollection, |
77 | pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>, | 60 | pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>, |
78 | pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, | 61 | pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, |
79 | pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, | ||
80 | pub(crate) shutdown_requested: bool, | 62 | pub(crate) shutdown_requested: bool, |
81 | pub(crate) status: Status, | 63 | pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>, |
82 | pub(crate) source_root_config: SourceRootConfig, | 64 | pub(crate) source_root_config: SourceRootConfig, |
83 | pub(crate) proc_macro_client: Option<ProcMacroClient>, | 65 | pub(crate) proc_macro_client: Option<ProcMacroClient>, |
84 | 66 | ||
85 | pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, | 67 | pub(crate) flycheck: Vec<FlycheckHandle>, |
86 | pub(crate) fetch_workspaces_queue: OpQueue<(), ()>, | 68 | pub(crate) flycheck_sender: Sender<flycheck::Message>, |
69 | pub(crate) flycheck_receiver: Receiver<flycheck::Message>, | ||
87 | 70 | ||
71 | pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, | ||
72 | pub(crate) vfs_config_version: u32, | ||
73 | pub(crate) vfs_progress_config_version: u32, | ||
74 | pub(crate) vfs_progress_n_total: usize, | ||
75 | pub(crate) vfs_progress_n_done: usize, | ||
76 | |||
77 | /// For both `workspaces` and `workspace_build_data`, the field stores the | ||
78 | /// data we actually use, while the `OpQueue` stores the result of the last | ||
79 | /// fetch. | ||
80 | /// | ||
81 | /// If the fetch (partially) fails, we do not update the values. | ||
82 | pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, | ||
83 | pub(crate) fetch_workspaces_queue: OpQueue<(), Vec<anyhow::Result<ProjectWorkspace>>>, | ||
88 | pub(crate) workspace_build_data: Option<BuildDataResult>, | 84 | pub(crate) workspace_build_data: Option<BuildDataResult>, |
89 | pub(crate) fetch_build_data_queue: OpQueue<BuildDataCollector, ()>, | 85 | pub(crate) fetch_build_data_queue: |
86 | OpQueue<BuildDataCollector, Option<anyhow::Result<BuildDataResult>>>, | ||
90 | 87 | ||
91 | latest_requests: Arc<RwLock<LatestRequests>>, | 88 | latest_requests: Arc<RwLock<LatestRequests>>, |
92 | } | 89 | } |
@@ -124,25 +121,32 @@ impl GlobalState { | |||
124 | GlobalState { | 121 | GlobalState { |
125 | sender, | 122 | sender, |
126 | req_queue: ReqQueue::default(), | 123 | req_queue: ReqQueue::default(), |
127 | vfs_config_version: 0, | ||
128 | task_pool, | 124 | task_pool, |
129 | loader, | 125 | loader, |
130 | flycheck: Vec::new(), | ||
131 | flycheck_sender, | ||
132 | flycheck_receiver, | ||
133 | config: Arc::new(config), | 126 | config: Arc::new(config), |
134 | analysis_host, | 127 | analysis_host, |
135 | diagnostics: Default::default(), | 128 | diagnostics: Default::default(), |
136 | mem_docs: FxHashMap::default(), | 129 | mem_docs: FxHashMap::default(), |
137 | semantic_tokens_cache: Arc::new(Default::default()), | 130 | semantic_tokens_cache: Arc::new(Default::default()), |
138 | vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), | ||
139 | shutdown_requested: false, | 131 | shutdown_requested: false, |
140 | status: Status::default(), | 132 | last_reported_status: None, |
141 | source_root_config: SourceRootConfig::default(), | 133 | source_root_config: SourceRootConfig::default(), |
142 | proc_macro_client: None, | 134 | proc_macro_client: None, |
135 | |||
136 | flycheck: Vec::new(), | ||
137 | flycheck_sender, | ||
138 | flycheck_receiver, | ||
139 | |||
140 | vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), | ||
141 | vfs_config_version: 0, | ||
142 | vfs_progress_config_version: 0, | ||
143 | vfs_progress_n_total: 0, | ||
144 | vfs_progress_n_done: 0, | ||
145 | |||
143 | workspaces: Arc::new(Vec::new()), | 146 | workspaces: Arc::new(Vec::new()), |
144 | fetch_workspaces_queue: OpQueue::default(), | 147 | fetch_workspaces_queue: OpQueue::default(), |
145 | workspace_build_data: None, | 148 | workspace_build_data: None, |
149 | |||
146 | fetch_build_data_queue: OpQueue::default(), | 150 | fetch_build_data_queue: OpQueue::default(), |
147 | latest_requests: Default::default(), | 151 | latest_requests: Default::default(), |
148 | } | 152 | } |
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 e88f16cc1..a5655116b 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -2,6 +2,7 @@ | |||
2 | //! requests/replies and notifications back to the client. | 2 | //! requests/replies and notifications back to the client. |
3 | use std::{ | 3 | use std::{ |
4 | env, fmt, | 4 | env, fmt, |
5 | sync::Arc, | ||
5 | time::{Duration, Instant}, | 6 | time::{Duration, Instant}, |
6 | }; | 7 | }; |
7 | 8 | ||
@@ -12,6 +13,7 @@ use ide::{Canceled, FileId}; | |||
12 | use ide_db::base_db::VfsPath; | 13 | use ide_db::base_db::VfsPath; |
13 | use lsp_server::{Connection, Notification, Request, Response}; | 14 | use lsp_server::{Connection, Notification, Request, Response}; |
14 | use lsp_types::notification::Notification as _; | 15 | use lsp_types::notification::Notification as _; |
16 | use project_model::BuildDataCollector; | ||
15 | use vfs::ChangeKind; | 17 | use vfs::ChangeKind; |
16 | 18 | ||
17 | use crate::{ | 19 | use crate::{ |
@@ -19,7 +21,7 @@ use crate::{ | |||
19 | dispatch::{NotificationDispatcher, RequestDispatcher}, | 21 | dispatch::{NotificationDispatcher, RequestDispatcher}, |
20 | document::DocumentData, | 22 | document::DocumentData, |
21 | from_proto, | 23 | from_proto, |
22 | global_state::{file_id_to_url, url_to_file_id, GlobalState, Status}, | 24 | global_state::{file_id_to_url, url_to_file_id, GlobalState}, |
23 | handlers, lsp_ext, | 25 | handlers, lsp_ext, |
24 | lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress}, | 26 | lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress}, |
25 | reload::{BuildDataProgress, ProjectWorkspaceProgress}, | 27 | reload::{BuildDataProgress, ProjectWorkspaceProgress}, |
@@ -187,7 +189,7 @@ impl GlobalState { | |||
187 | log::info!("task queue len: {}", task_queue_len); | 189 | log::info!("task queue len: {}", task_queue_len); |
188 | } | 190 | } |
189 | 191 | ||
190 | let mut new_status = self.status; | 192 | let was_quiescent = self.is_quiescent(); |
191 | match event { | 193 | match event { |
192 | Event::Lsp(msg) => match msg { | 194 | Event::Lsp(msg) => match msg { |
193 | lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, | 195 | lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, |
@@ -227,11 +229,24 @@ impl GlobalState { | |||
227 | (Progress::Report, Some(msg)) | 229 | (Progress::Report, Some(msg)) |
228 | } | 230 | } |
229 | ProjectWorkspaceProgress::End(workspaces) => { | 231 | ProjectWorkspaceProgress::End(workspaces) => { |
230 | self.fetch_workspaces_completed(); | 232 | self.fetch_workspaces_completed(workspaces); |
231 | self.switch_workspaces(workspaces, None); | 233 | |
234 | let old = Arc::clone(&self.workspaces); | ||
235 | self.switch_workspaces(); | ||
236 | let workspaces_updated = !Arc::ptr_eq(&old, &self.workspaces); | ||
237 | |||
238 | if self.config.run_build_scripts() && workspaces_updated { | ||
239 | let mut collector = BuildDataCollector::default(); | ||
240 | for ws in self.workspaces.iter() { | ||
241 | ws.collect_build_data_configs(&mut collector); | ||
242 | } | ||
243 | self.fetch_build_data_request(collector) | ||
244 | } | ||
245 | |||
232 | (Progress::End, None) | 246 | (Progress::End, None) |
233 | } | 247 | } |
234 | }; | 248 | }; |
249 | |||
235 | self.report_progress("fetching", state, msg, None); | 250 | self.report_progress("fetching", state, msg, None); |
236 | } | 251 | } |
237 | Task::FetchBuildData(progress) => { | 252 | Task::FetchBuildData(progress) => { |
@@ -240,19 +255,21 @@ impl GlobalState { | |||
240 | BuildDataProgress::Report(msg) => { | 255 | BuildDataProgress::Report(msg) => { |
241 | (Some(Progress::Report), Some(msg)) | 256 | (Some(Progress::Report), Some(msg)) |
242 | } | 257 | } |
243 | BuildDataProgress::End(collector) => { | 258 | BuildDataProgress::End(build_data_result) => { |
244 | self.fetch_build_data_completed(); | 259 | self.fetch_build_data_completed(build_data_result); |
245 | let workspaces = | 260 | |
246 | (*self.workspaces).clone().into_iter().map(Ok).collect(); | 261 | self.switch_workspaces(); |
247 | self.switch_workspaces(workspaces, Some(collector)); | 262 | |
248 | (Some(Progress::End), None) | 263 | (Some(Progress::End), None) |
249 | } | 264 | } |
250 | }; | 265 | }; |
266 | |||
251 | if let Some(state) = state { | 267 | if let Some(state) = state { |
252 | self.report_progress("loading", state, msg, None); | 268 | self.report_progress("loading", state, msg, None); |
253 | } | 269 | } |
254 | } | 270 | } |
255 | } | 271 | } |
272 | |||
256 | // Coalesce multiple task events into one loop turn | 273 | // Coalesce multiple task events into one loop turn |
257 | task = match self.task_pool.receiver.try_recv() { | 274 | task = match self.task_pool.receiver.try_recv() { |
258 | Ok(task) => task, | 275 | Ok(task) => task, |
@@ -298,30 +315,25 @@ impl GlobalState { | |||
298 | } | 315 | } |
299 | vfs::loader::Message::Progress { n_total, n_done, config_version } => { | 316 | vfs::loader::Message::Progress { n_total, n_done, config_version } => { |
300 | always!(config_version <= self.vfs_config_version); | 317 | always!(config_version <= self.vfs_config_version); |
301 | if n_total == 0 { | 318 | |
302 | new_status = Status::Invalid; | 319 | self.vfs_progress_config_version = config_version; |
320 | self.vfs_progress_n_total = n_total; | ||
321 | self.vfs_progress_n_done = n_done; | ||
322 | |||
323 | let state = if n_done == 0 { | ||
324 | Progress::Begin | ||
325 | } else if n_done < n_total { | ||
326 | Progress::Report | ||
303 | } else { | 327 | } else { |
304 | let state = if n_done == 0 { | 328 | assert_eq!(n_done, n_total); |
305 | new_status = Status::Loading; | 329 | Progress::End |
306 | Progress::Begin | 330 | }; |
307 | } else if n_done < n_total { | 331 | self.report_progress( |
308 | Progress::Report | 332 | "roots scanned", |
309 | } else { | 333 | state, |
310 | assert_eq!(n_done, n_total); | 334 | Some(format!("{}/{}", n_done, n_total)), |
311 | new_status = Status::Ready { | 335 | Some(Progress::fraction(n_done, n_total)), |
312 | partial: self.config.run_build_scripts() | 336 | ) |
313 | && self.workspace_build_data.is_none() | ||
314 | || config_version < self.vfs_config_version, | ||
315 | }; | ||
316 | Progress::End | ||
317 | }; | ||
318 | self.report_progress( | ||
319 | "roots scanned", | ||
320 | state, | ||
321 | Some(format!("{}/{}", n_done, n_total)), | ||
322 | Some(Progress::fraction(n_done, n_total)), | ||
323 | ) | ||
324 | } | ||
325 | } | 337 | } |
326 | } | 338 | } |
327 | // Coalesce many VFS event into a single loop turn | 339 | // Coalesce many VFS event into a single loop turn |
@@ -397,18 +409,14 @@ impl GlobalState { | |||
397 | } | 409 | } |
398 | 410 | ||
399 | let state_changed = self.process_changes(); | 411 | let state_changed = self.process_changes(); |
400 | let prev_status = self.status; | 412 | |
401 | if prev_status != new_status { | 413 | if self.is_quiescent() && !was_quiescent { |
402 | self.transition(new_status); | ||
403 | } | ||
404 | let is_ready = matches!(self.status, Status::Ready { .. }); | ||
405 | if prev_status == Status::Loading && is_ready { | ||
406 | for flycheck in &self.flycheck { | 414 | for flycheck in &self.flycheck { |
407 | flycheck.update(); | 415 | flycheck.update(); |
408 | } | 416 | } |
409 | } | 417 | } |
410 | 418 | ||
411 | if is_ready && (state_changed || prev_status == Status::Loading) { | 419 | if self.is_quiescent() && (!was_quiescent || state_changed) { |
412 | self.update_file_notifications_on_threadpool(); | 420 | self.update_file_notifications_on_threadpool(); |
413 | 421 | ||
414 | // Refresh semantic tokens if the client supports it. | 422 | // Refresh semantic tokens if the client supports it. |
@@ -437,9 +445,13 @@ impl GlobalState { | |||
437 | } | 445 | } |
438 | } | 446 | } |
439 | 447 | ||
440 | self.fetch_workspaces_if_needed(); | 448 | if self.config.cargo_autoreload() { |
449 | self.fetch_workspaces_if_needed(); | ||
450 | } | ||
441 | self.fetch_build_data_if_needed(); | 451 | self.fetch_build_data_if_needed(); |
442 | 452 | ||
453 | self.report_new_status_if_needed(); | ||
454 | |||
443 | let loop_duration = loop_start.elapsed(); | 455 | let loop_duration = loop_start.elapsed(); |
444 | if loop_duration > Duration::from_millis(100) { | 456 | if loop_duration > Duration::from_millis(100) { |
445 | log::warn!("overly long loop turn: {:?}", loop_duration); | 457 | log::warn!("overly long loop turn: {:?}", loop_duration); |
@@ -466,7 +478,8 @@ impl GlobalState { | |||
466 | return Ok(()); | 478 | return Ok(()); |
467 | } | 479 | } |
468 | 480 | ||
469 | 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() { | ||
470 | self.respond(lsp_server::Response::new_err( | 483 | self.respond(lsp_server::Response::new_err( |
471 | req.id, | 484 | req.id, |
472 | // FIXME: i32 should impl From<ErrorCode> (from() guarantees lossless conversion) | 485 | // FIXME: i32 should impl From<ErrorCode> (from() guarantees lossless conversion) |
@@ -477,7 +490,11 @@ impl GlobalState { | |||
477 | } | 490 | } |
478 | 491 | ||
479 | RequestDispatcher { req: Some(req), global_state: self } | 492 | RequestDispatcher { req: Some(req), global_state: self } |
480 | .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| Ok(s.fetch_workspaces_request()))? | 493 | .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| { |
494 | s.fetch_workspaces_request(); | ||
495 | s.fetch_workspaces_if_needed(); | ||
496 | Ok(()) | ||
497 | })? | ||
481 | .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))? | 498 | .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))? |
482 | .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))? | 499 | .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))? |
483 | .on_sync::<lsp_types::request::Shutdown>(|s, ()| { | 500 | .on_sync::<lsp_types::request::Shutdown>(|s, ()| { |
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 d12f891bb..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,25 +52,17 @@ 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 | ); |
63 | if self.config.cargo_autoreload() { | 65 | self.fetch_workspaces_request(); |
64 | self.fetch_workspaces_request(); | ||
65 | } else { | ||
66 | self.transition(Status::NeedsReload); | ||
67 | } | ||
68 | 66 | ||
69 | fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool { | 67 | fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool { |
70 | const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"]; | 68 | const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"]; |
@@ -101,46 +99,32 @@ impl GlobalState { | |||
101 | false | 99 | false |
102 | } | 100 | } |
103 | } | 101 | } |
104 | pub(crate) fn transition(&mut self, new_status: Status) { | 102 | pub(crate) fn report_new_status_if_needed(&mut self) { |
105 | self.status = new_status; | 103 | if !self.config.server_status_notification() { |
106 | if self.config.status_notification() { | 104 | return; |
107 | let lsp_status = match new_status { | ||
108 | Status::Loading => lsp_ext::Status::Loading, | ||
109 | Status::Ready { partial: true } => lsp_ext::Status::ReadyPartial, | ||
110 | Status::Ready { partial: false } => lsp_ext::Status::Ready, | ||
111 | Status::Invalid => lsp_ext::Status::Invalid, | ||
112 | Status::NeedsReload => lsp_ext::Status::NeedsReload, | ||
113 | }; | ||
114 | self.send_notification::<lsp_ext::StatusNotification>(StatusParams { | ||
115 | status: lsp_status, | ||
116 | }); | ||
117 | } | 105 | } |
118 | } | ||
119 | |||
120 | pub(crate) fn fetch_build_data_request(&mut self, build_data_collector: BuildDataCollector) { | ||
121 | self.fetch_build_data_queue.request_op(build_data_collector); | ||
122 | } | ||
123 | 106 | ||
124 | pub(crate) fn fetch_build_data_if_needed(&mut self) { | 107 | let mut status = lsp_ext::ServerStatusParams { |
125 | let mut build_data_collector = match self.fetch_build_data_queue.should_start_op() { | 108 | health: lsp_ext::Health::Ok, |
126 | Some(it) => it, | 109 | quiescent: self.is_quiescent(), |
127 | None => return, | 110 | message: None, |
128 | }; | 111 | }; |
129 | self.task_pool.handle.spawn_with_sender(move |sender| { | 112 | if !self.config.cargo_autoreload() |
130 | sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap(); | 113 | && self.is_quiescent() |
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 | } | ||
131 | 123 | ||
132 | let progress = { | 124 | if self.last_reported_status.as_ref() != Some(&status) { |
133 | let sender = sender.clone(); | 125 | self.last_reported_status = Some(status.clone()); |
134 | move |msg| { | 126 | self.send_notification::<lsp_ext::ServerStatusNotification>(status); |
135 | sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap() | 127 | } |
136 | } | ||
137 | }; | ||
138 | let res = build_data_collector.collect(&progress); | ||
139 | sender.send(Task::FetchBuildData(BuildDataProgress::End(res))).unwrap(); | ||
140 | }); | ||
141 | } | ||
142 | pub(crate) fn fetch_build_data_completed(&mut self) { | ||
143 | self.fetch_build_data_queue.op_completed(()) | ||
144 | } | 128 | } |
145 | 129 | ||
146 | pub(crate) fn fetch_workspaces_request(&mut self) { | 130 | pub(crate) fn fetch_workspaces_request(&mut self) { |
@@ -194,57 +178,69 @@ impl GlobalState { | |||
194 | } | 178 | } |
195 | }); | 179 | }); |
196 | } | 180 | } |
197 | pub(crate) fn fetch_workspaces_completed(&mut self) { | 181 | pub(crate) fn fetch_workspaces_completed( |
198 | self.fetch_workspaces_queue.op_completed(()) | 182 | &mut self, |
183 | workspaces: Vec<anyhow::Result<ProjectWorkspace>>, | ||
184 | ) { | ||
185 | self.fetch_workspaces_queue.op_completed(workspaces) | ||
199 | } | 186 | } |
200 | 187 | ||
201 | pub(crate) fn switch_workspaces( | 188 | pub(crate) fn fetch_build_data_request(&mut self, build_data_collector: BuildDataCollector) { |
189 | self.fetch_build_data_queue.request_op(build_data_collector); | ||
190 | } | ||
191 | pub(crate) fn fetch_build_data_if_needed(&mut self) { | ||
192 | let mut build_data_collector = match self.fetch_build_data_queue.should_start_op() { | ||
193 | Some(it) => it, | ||
194 | None => return, | ||
195 | }; | ||
196 | self.task_pool.handle.spawn_with_sender(move |sender| { | ||
197 | sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap(); | ||
198 | |||
199 | let progress = { | ||
200 | let sender = sender.clone(); | ||
201 | move |msg| { | ||
202 | sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap() | ||
203 | } | ||
204 | }; | ||
205 | let res = build_data_collector.collect(&progress); | ||
206 | sender.send(Task::FetchBuildData(BuildDataProgress::End(res))).unwrap(); | ||
207 | }); | ||
208 | } | ||
209 | pub(crate) fn fetch_build_data_completed( | ||
202 | &mut self, | 210 | &mut self, |
203 | workspaces: Vec<anyhow::Result<ProjectWorkspace>>, | 211 | build_data: anyhow::Result<BuildDataResult>, |
204 | workspace_build_data: Option<anyhow::Result<BuildDataResult>>, | ||
205 | ) { | 212 | ) { |
206 | let _p = profile::span("GlobalState::switch_workspaces"); | 213 | self.fetch_build_data_queue.op_completed(Some(build_data)) |
207 | log::info!("will switch workspaces: {:?}", workspaces); | 214 | } |
208 | 215 | ||
209 | let mut has_errors = false; | 216 | pub(crate) fn switch_workspaces(&mut self) { |
210 | let workspaces = workspaces | 217 | let _p = profile::span("GlobalState::switch_workspaces"); |
211 | .into_iter() | 218 | log::info!("will switch workspaces"); |
212 | .filter_map(|res| { | ||
213 | res.map_err(|err| { | ||
214 | has_errors = true; | ||
215 | log::error!("failed to load workspace: {:#}", err); | ||
216 | if self.workspaces.is_empty() { | ||
217 | self.show_message( | ||
218 | lsp_types::MessageType::Error, | ||
219 | format!("rust-analyzer failed to load workspace: {:#}", err), | ||
220 | ); | ||
221 | } | ||
222 | }) | ||
223 | .ok() | ||
224 | }) | ||
225 | .collect::<Vec<_>>(); | ||
226 | 219 | ||
227 | let workspace_build_data = match workspace_build_data { | 220 | if let Some(error_message) = self.loading_error() { |
228 | Some(Ok(it)) => Some(it), | 221 | log::error!("failed to switch workspaces: {}", error_message); |
229 | Some(Err(err)) => { | 222 | self.show_message(lsp_types::MessageType::Error, error_message); |
230 | log::error!("failed to fetch build data: {:#}", err); | 223 | if !self.workspaces.is_empty() { |
231 | self.show_message( | ||
232 | lsp_types::MessageType::Error, | ||
233 | format!("rust-analyzer failed to fetch build data: {:#}", err), | ||
234 | ); | ||
235 | return; | 224 | return; |
236 | } | 225 | } |
237 | None => None, | 226 | } |
227 | |||
228 | let workspaces = self | ||
229 | .fetch_workspaces_queue | ||
230 | .last_op_result() | ||
231 | .iter() | ||
232 | .filter_map(|res| res.as_ref().ok().cloned()) | ||
233 | .collect::<Vec<_>>(); | ||
234 | |||
235 | let workspace_build_data = match self.fetch_build_data_queue.last_op_result() { | ||
236 | Some(Ok(it)) => Some(it.clone()), | ||
237 | None | Some(Err(_)) => None, | ||
238 | }; | 238 | }; |
239 | 239 | ||
240 | if *self.workspaces == workspaces && self.workspace_build_data == workspace_build_data { | 240 | if *self.workspaces == workspaces && self.workspace_build_data == workspace_build_data { |
241 | return; | 241 | return; |
242 | } | 242 | } |
243 | 243 | ||
244 | if !self.workspaces.is_empty() && has_errors { | ||
245 | return; | ||
246 | } | ||
247 | |||
248 | if let FilesWatcher::Client = self.config.files().watcher { | 244 | if let FilesWatcher::Client = self.config.files().watcher { |
249 | if self.config.did_change_watched_files_dynamic_registration() { | 245 | if self.config.did_change_watched_files_dynamic_registration() { |
250 | let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { | 246 | let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { |
@@ -337,14 +333,6 @@ impl GlobalState { | |||
337 | }; | 333 | }; |
338 | change.set_crate_graph(crate_graph); | 334 | change.set_crate_graph(crate_graph); |
339 | 335 | ||
340 | if self.config.run_build_scripts() && workspace_build_data.is_none() { | ||
341 | let mut collector = BuildDataCollector::default(); | ||
342 | for ws in &workspaces { | ||
343 | ws.collect_build_data_configs(&mut collector); | ||
344 | } | ||
345 | self.fetch_build_data_request(collector) | ||
346 | } | ||
347 | |||
348 | self.source_root_config = project_folders.source_root_config; | 336 | self.source_root_config = project_folders.source_root_config; |
349 | self.workspaces = Arc::new(workspaces); | 337 | self.workspaces = Arc::new(workspaces); |
350 | self.workspace_build_data = workspace_build_data; | 338 | self.workspace_build_data = workspace_build_data; |
@@ -355,6 +343,24 @@ impl GlobalState { | |||
355 | log::info!("did switch workspaces"); | 343 | log::info!("did switch workspaces"); |
356 | } | 344 | } |
357 | 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 | |||
358 | fn reload_flycheck(&mut self) { | 364 | fn reload_flycheck(&mut self) { |
359 | let _p = profile::span("GlobalState::reload_flycheck"); | 365 | let _p = profile::span("GlobalState::reload_flycheck"); |
360 | 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..bd023f803 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..e453bb9e0 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 | ||