aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/project_model/src/build_data.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs4
-rw-r--r--crates/rust-analyzer/src/global_state.rs62
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs30
-rw-r--r--crates/rust-analyzer/src/main_loop.rs101
-rw-r--r--crates/rust-analyzer/src/op_queue.rs16
-rw-r--r--crates/rust-analyzer/src/reload.rs194
-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
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)]
56pub struct BuildDataResult { 56pub 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)]
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,
@@ -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
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 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.
3use std::{ 3use 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};
12use ide_db::base_db::VfsPath; 13use ide_db::base_db::VfsPath;
13use lsp_server::{Connection, Notification, Request, Response}; 14use lsp_server::{Connection, Notification, Request, Response};
14use lsp_types::notification::Notification as _; 15use lsp_types::notification::Notification as _;
16use project_model::BuildDataCollector;
15use vfs::ChangeKind; 17use vfs::ChangeKind;
16 18
17use crate::{ 19use 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
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 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
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,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<!---
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..bd023f803 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..e453bb9e0 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