]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/reload.rs
More robust status notifications
[rust.git] / crates / rust-analyzer / src / reload.rs
index 0507186dce52911a00c34fb7ed990836d7d9d3e6..301c7003b99dafa325467925aa29c2a49746203d 100644 (file)
@@ -4,16 +4,15 @@
 use flycheck::{FlycheckConfig, FlycheckHandle};
 use ide::Change;
 use ide_db::base_db::{CrateGraph, SourceRoot, VfsPath};
-use project_model::{ProcMacroClient, ProjectWorkspace};
+use project_model::{BuildDataCollector, BuildDataResult, ProcMacroClient, ProjectWorkspace};
 use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
 
 use crate::{
     config::{Config, FilesWatcher, LinkedProject},
-    global_state::{GlobalState, Status},
+    global_state::GlobalState,
     lsp_ext,
     main_loop::Task,
 };
-use lsp_ext::StatusParams;
 
 #[derive(Debug)]
 pub(crate) enum ProjectWorkspaceProgress {
@@ -22,7 +21,21 @@ pub(crate) enum ProjectWorkspaceProgress {
     End(Vec<anyhow::Result<ProjectWorkspace>>),
 }
 
+#[derive(Debug)]
+pub(crate) enum BuildDataProgress {
+    Begin,
+    Report(String),
+    End(anyhow::Result<BuildDataResult>),
+}
+
 impl GlobalState {
+    pub(crate) fn is_quiescent(&self) -> bool {
+        !(self.fetch_workspaces_queue.op_in_progress()
+            || self.fetch_build_data_queue.op_in_progress()
+            || self.vfs_progress_config_version < self.vfs_config_version
+            || self.vfs_progress_n_done < self.vfs_progress_n_total)
+    }
+
     pub(crate) fn update_configuration(&mut self, config: Config) {
         let _p = profile::span("GlobalState::update_configuration");
         let old_config = mem::replace(&mut self.config, Arc::new(config));
@@ -39,15 +52,17 @@ pub(crate) fn maybe_refresh(&mut self, changes: &[(AbsPathBuf, ChangeKind)]) {
         if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
             return;
         }
-        match self.status {
-            Status::Loading | Status::NeedsReload => return,
-            Status::Ready | Status::Invalid => (),
-        }
-        if self.config.cargo_autoreload() {
-            self.fetch_workspaces_request();
-        } else {
-            self.transition(Status::NeedsReload);
-        }
+        log::info!(
+            "Requesting workspace reload because of the following changes: {}",
+            itertools::join(
+                changes
+                    .iter()
+                    .filter(|(path, kind)| is_interesting(path, *kind))
+                    .map(|(path, kind)| format!("{}: {:?}", path.display(), kind)),
+                ", "
+            )
+        );
+        self.fetch_workspaces_request();
 
         fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool {
             const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
@@ -84,29 +99,42 @@ fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool {
             false
         }
     }
-    pub(crate) fn transition(&mut self, new_status: Status) {
-        self.status = new_status;
-        if self.config.status_notification() {
-            let lsp_status = match new_status {
-                Status::Loading => lsp_ext::Status::Loading,
-                Status::Ready => lsp_ext::Status::Ready,
-                Status::Invalid => lsp_ext::Status::Invalid,
-                Status::NeedsReload => lsp_ext::Status::NeedsReload,
-            };
-            self.send_notification::<lsp_ext::StatusNotification>(StatusParams {
-                status: lsp_status,
-            });
+    pub(crate) fn report_new_status_if_needed(&mut self) {
+        if !self.config.server_status_notification() {
+            return;
+        }
+
+        let mut status = lsp_ext::ServerStatusParams {
+            health: lsp_ext::Health::Ok,
+            quiescent: self.is_quiescent(),
+            message: None,
+        };
+        if !self.config.cargo_autoreload()
+            && self.is_quiescent()
+            && self.fetch_workspaces_queue.op_requested()
+        {
+            status.health = lsp_ext::Health::Warning;
+            status.message = Some("Workspace reload required".to_string())
+        }
+        if let Some(error) = self.loading_error() {
+            status.health = lsp_ext::Health::Error;
+            status.message = Some(format!("Workspace reload failed: {}", error))
+        }
+
+        if self.last_reported_status.as_ref() != Some(&status) {
+            self.last_reported_status = Some(status.clone());
+            self.send_notification::<lsp_ext::ServerStatusNotification>(status);
         }
     }
 
     pub(crate) fn fetch_workspaces_request(&mut self) {
-        self.fetch_workspaces_queue.request_op()
+        self.fetch_workspaces_queue.request_op(())
     }
     pub(crate) fn fetch_workspaces_if_needed(&mut self) {
-        log::info!("will fetch workspaces");
-        if !self.fetch_workspaces_queue.should_start_op() {
+        if self.fetch_workspaces_queue.should_start_op().is_none() {
             return;
         }
+        log::info!("will fetch workspaces");
 
         self.task_pool.handle.spawn_with_sender({
             let linked_projects = self.config.linked_projects();
@@ -150,37 +178,66 @@ pub(crate) fn fetch_workspaces_if_needed(&mut self) {
             }
         });
     }
-    pub(crate) fn fetch_workspaces_completed(&mut self) {
-        self.fetch_workspaces_queue.op_completed()
+    pub(crate) fn fetch_workspaces_completed(
+        &mut self,
+        workspaces: Vec<anyhow::Result<ProjectWorkspace>>,
+    ) {
+        self.fetch_workspaces_queue.op_completed(workspaces)
+    }
+
+    pub(crate) fn fetch_build_data_request(&mut self, build_data_collector: BuildDataCollector) {
+        self.fetch_build_data_queue.request_op(build_data_collector);
+    }
+    pub(crate) fn fetch_build_data_if_needed(&mut self) {
+        let mut build_data_collector = match self.fetch_build_data_queue.should_start_op() {
+            Some(it) => it,
+            None => return,
+        };
+        self.task_pool.handle.spawn_with_sender(move |sender| {
+            sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
+
+            let progress = {
+                let sender = sender.clone();
+                move |msg| {
+                    sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
+                }
+            };
+            let res = build_data_collector.collect(&progress);
+            sender.send(Task::FetchBuildData(BuildDataProgress::End(res))).unwrap();
+        });
+    }
+    pub(crate) fn fetch_build_data_completed(
+        &mut self,
+        build_data: anyhow::Result<BuildDataResult>,
+    ) {
+        self.fetch_build_data_queue.op_completed(Some(build_data))
     }
 
-    pub(crate) fn switch_workspaces(&mut self, workspaces: Vec<anyhow::Result<ProjectWorkspace>>) {
+    pub(crate) fn switch_workspaces(&mut self) {
         let _p = profile::span("GlobalState::switch_workspaces");
-        log::info!("will switch workspaces: {:?}", workspaces);
+        log::info!("will switch workspaces");
 
-        let mut has_errors = false;
-        let workspaces = workspaces
-            .into_iter()
-            .filter_map(|res| {
-                res.map_err(|err| {
-                    has_errors = true;
-                    log::error!("failed to load workspace: {:#}", err);
-                    if self.workspaces.is_empty() {
-                        self.show_message(
-                            lsp_types::MessageType::Error,
-                            format!("rust-analyzer failed to load workspace: {:#}", err),
-                        );
-                    }
-                })
-                .ok()
-            })
+        if let Some(error_message) = self.loading_error() {
+            log::error!("failed to switch workspaces: {}", error_message);
+            self.show_message(lsp_types::MessageType::Error, error_message);
+            if !self.workspaces.is_empty() {
+                return;
+            }
+        }
+
+        let workspaces = self
+            .fetch_workspaces_queue
+            .last_op_result()
+            .iter()
+            .filter_map(|res| res.as_ref().ok().cloned())
             .collect::<Vec<_>>();
 
-        if &*self.workspaces == &workspaces {
-            return;
-        }
+        let workspace_build_data = match self.fetch_build_data_queue.last_op_result() {
+            Some(Ok(it)) => Some(it.clone()),
+            None | Some(Err(_)) => None,
+        };
 
-        if !self.workspaces.is_empty() && has_errors {
+        if *self.workspaces == workspaces && self.workspace_build_data == workspace_build_data {
             return;
         }
 
@@ -189,7 +246,7 @@ pub(crate) fn switch_workspaces(&mut self, workspaces: Vec<anyhow::Result<Projec
                 let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
                     watchers: workspaces
                         .iter()
-                        .flat_map(ProjectWorkspace::to_roots)
+                        .flat_map(|it| it.to_roots(workspace_build_data.as_ref()))
                         .filter(|it| it.is_member)
                         .flat_map(|root| {
                             root.include.into_iter().map(|it| format!("{}/**/*.rs", it.display()))
@@ -215,28 +272,36 @@ pub(crate) fn switch_workspaces(&mut self, workspaces: Vec<anyhow::Result<Projec
         let mut change = Change::new();
 
         let files_config = self.config.files();
-        let project_folders = ProjectFolders::new(&workspaces, &files_config.exclude);
-
-        self.proc_macro_client = match self.config.proc_macro_srv() {
-            None => None,
-            Some((path, args)) => match ProcMacroClient::extern_process(path.clone(), args) {
-                Ok(it) => Some(it),
-                Err(err) => {
-                    log::error!(
-                        "Failed to run proc_macro_srv from path {}, error: {:?}",
-                        path.display(),
-                        err
-                    );
-                    None
-                }
-            },
-        };
+        let project_folders =
+            ProjectFolders::new(&workspaces, &files_config.exclude, workspace_build_data.as_ref());
+
+        if self.proc_macro_client.is_none() {
+            self.proc_macro_client = match self.config.proc_macro_srv() {
+                None => None,
+                Some((path, args)) => match ProcMacroClient::extern_process(path.clone(), args) {
+                    Ok(it) => Some(it),
+                    Err(err) => {
+                        log::error!(
+                            "Failed to run proc_macro_srv from path {}, error: {:?}",
+                            path.display(),
+                            err
+                        );
+                        None
+                    }
+                },
+            };
+        }
 
         let watch = match files_config.watcher {
             FilesWatcher::Client => vec![],
             FilesWatcher::Notify => project_folders.watch,
         };
-        self.loader.handle.set_config(vfs::loader::Config { load: project_folders.load, watch });
+        self.vfs_config_version += 1;
+        self.loader.handle.set_config(vfs::loader::Config {
+            load: project_folders.load,
+            watch,
+            version: self.vfs_config_version,
+        });
 
         // Create crate graph from all the workspaces
         let crate_graph = {
@@ -257,7 +322,11 @@ pub(crate) fn switch_workspaces(&mut self, workspaces: Vec<anyhow::Result<Projec
                 res
             };
             for ws in workspaces.iter() {
-                crate_graph.extend(ws.to_crate_graph(self.proc_macro_client.as_ref(), &mut load));
+                crate_graph.extend(ws.to_crate_graph(
+                    workspace_build_data.as_ref(),
+                    self.proc_macro_client.as_ref(),
+                    &mut load,
+                ));
             }
 
             crate_graph
@@ -266,6 +335,7 @@ pub(crate) fn switch_workspaces(&mut self, workspaces: Vec<anyhow::Result<Projec
 
         self.source_root_config = project_folders.source_root_config;
         self.workspaces = Arc::new(workspaces);
+        self.workspace_build_data = workspace_build_data;
 
         self.analysis_host.apply_change(change);
         self.process_changes();
@@ -273,6 +343,24 @@ pub(crate) fn switch_workspaces(&mut self, workspaces: Vec<anyhow::Result<Projec
         log::info!("did switch workspaces");
     }
 
+    fn loading_error(&self) -> Option<String> {
+        let mut message = None;
+
+        for ws in self.fetch_workspaces_queue.last_op_result() {
+            if let Err(err) = ws {
+                let message = message.get_or_insert_with(String::new);
+                stdx::format_to!(message, "rust-analyzer failed to load workspace: {:#}\n", err);
+            }
+        }
+
+        if let Some(Err(err)) = self.fetch_build_data_queue.last_op_result() {
+            let message = message.get_or_insert_with(String::new);
+            stdx::format_to!(message, "rust-analyzer failed to fetch build data: {:#}\n", err);
+        }
+
+        message
+    }
+
     fn reload_flycheck(&mut self) {
         let _p = profile::span("GlobalState::reload_flycheck");
         let config = match self.config.flycheck() {
@@ -323,12 +411,13 @@ impl ProjectFolders {
     pub(crate) fn new(
         workspaces: &[ProjectWorkspace],
         global_excludes: &[AbsPathBuf],
+        build_data: Option<&BuildDataResult>,
     ) -> ProjectFolders {
         let mut res = ProjectFolders::default();
         let mut fsc = FileSetConfig::builder();
         let mut local_filesets = vec![];
 
-        for root in workspaces.iter().flat_map(|it| it.to_roots()) {
+        for root in workspaces.iter().flat_map(|it| it.to_roots(build_data)) {
             let file_set_roots: Vec<VfsPath> =
                 root.include.iter().cloned().map(VfsPath::from).collect();