]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/main_loop.rs
fix: correctly update diagnostics when files are opened and closed
[rust.git] / crates / rust-analyzer / src / main_loop.rs
index f837b89ddc91f88e9f0ef4343c5c136297467bae..35fce79f5eb6a4dafaa1baf4e9c53d36e3bc7249 100644 (file)
 use crossbeam_channel::{select, Receiver};
 use ide::{FileId, PrimeCachesProgress};
 use ide_db::base_db::VfsPath;
-use lsp_server::{Connection, Notification, Request, Response};
+use lsp_server::{Connection, Notification, Request};
 use lsp_types::notification::Notification as _;
-use project_model::BuildDataCollector;
 use vfs::ChangeKind;
 
 use crate::{
     config::Config,
     dispatch::{NotificationDispatcher, RequestDispatcher},
-    document::DocumentData,
     from_proto,
     global_state::{file_id_to_url, url_to_file_id, GlobalState},
     handlers, lsp_ext,
-    lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress},
+    lsp_utils::{apply_document_changes, is_cancelled, notification_is, Progress},
+    mem_docs::DocumentData,
     reload::{BuildDataProgress, ProjectWorkspaceProgress},
     Result,
 };
@@ -61,7 +60,7 @@ enum Event {
 
 #[derive(Debug)]
 pub(crate) enum Task {
-    Response(Response),
+    Response(lsp_server::Response),
     Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
     PrimeCaches(PrimeCachesProgress),
     FetchWorkspace(ProjectWorkspaceProgress),
@@ -103,6 +102,7 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 impl GlobalState {
     fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
         if self.config.linked_projects().is_empty()
+            && self.config.detached_files().is_empty()
             && self.config.notifications().cargo_toml_not_found
         {
             self.show_message(
@@ -235,12 +235,7 @@ fn handle_event(&mut self, event: Event) -> Result<()> {
                                     let workspaces_updated = !Arc::ptr_eq(&old, &self.workspaces);
 
                                     if self.config.run_build_scripts() && workspaces_updated {
-                                        let mut collector =
-                                            BuildDataCollector::new(self.config.wrap_rustc());
-                                        for ws in self.workspaces.iter() {
-                                            ws.collect_build_data_configs(&mut collector);
-                                        }
-                                        self.fetch_build_data_request(collector)
+                                        self.fetch_build_data_request()
                                     }
 
                                     (Progress::End, None)
@@ -310,7 +305,7 @@ fn handle_event(&mut self, event: Event) -> Result<()> {
                             let vfs = &mut self.vfs.write().0;
                             for (path, contents) in files {
                                 let path = VfsPath::from(path);
-                                if !self.mem_docs.contains_key(&path) {
+                                if !self.mem_docs.contains(&path) {
                                     vfs.set_file_contents(path, contents);
                                 }
                             }
@@ -411,25 +406,49 @@ fn handle_event(&mut self, event: Event) -> Result<()> {
         }
 
         let state_changed = self.process_changes();
+        let memdocs_added_or_removed = self.mem_docs.take_changes();
 
-        if self.is_quiescent() && !was_quiescent {
-            for flycheck in &self.flycheck {
-                flycheck.update();
+        if self.is_quiescent() {
+            if !was_quiescent {
+                for flycheck in &self.flycheck {
+                    flycheck.update();
+                }
             }
-        }
 
-        if self.is_quiescent() && (!was_quiescent || state_changed) {
-            self.update_file_notifications_on_threadpool();
+            if !was_quiescent || state_changed {
+                // Ensure that only one cache priming task can run at a time
+                self.prime_caches_queue.request_op();
+                if self.prime_caches_queue.should_start_op() {
+                    self.task_pool.handle.spawn_with_sender({
+                        let snap = self.snapshot();
+                        move |sender| {
+                            let cb = |progress| {
+                                sender.send(Task::PrimeCaches(progress)).unwrap();
+                            };
+                            match snap.analysis.prime_caches(cb) {
+                                Ok(()) => (),
+                                Err(_canceled) => (),
+                            }
+                        }
+                    });
+                }
+
+                // Refresh semantic tokens if the client supports it.
+                if self.config.semantic_tokens_refresh() {
+                    self.semantic_tokens_cache.lock().clear();
+                    self.send_request::<lsp_types::request::SemanticTokensRefesh>((), |_, _| ());
+                }
 
-            // Refresh semantic tokens if the client supports it.
-            if self.config.semantic_tokens_refresh() {
-                self.semantic_tokens_cache.lock().clear();
-                self.send_request::<lsp_types::request::SemanticTokensRefesh>((), |_, _| ());
+                // Refresh code lens if the client supports it.
+                if self.config.code_lens_refresh() {
+                    self.send_request::<lsp_types::request::CodeLensRefresh>((), |_, _| ());
+                }
             }
 
-            // Refresh code lens if the client supports it.
-            if self.config.code_lens_refresh() {
-                self.send_request::<lsp_types::request::CodeLensRefresh>((), |_, _| ());
+            if !was_quiescent || state_changed || memdocs_added_or_removed {
+                if self.config.publish_diagnostics() {
+                    self.update_diagnostics()
+                }
             }
         }
 
@@ -471,7 +490,7 @@ fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()>
         self.register_request(&req, request_received);
 
         if self.shutdown_requested {
-            self.respond(Response::new_err(
+            self.respond(lsp_server::Response::new_err(
                 req.id,
                 lsp_server::ErrorCode::InvalidRequest as i32,
                 "Shutdown already requested.".to_owned(),
@@ -530,6 +549,7 @@ fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()>
             .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
             .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
             .on::<lsp_types::request::GotoDefinition>(handlers::handle_goto_definition)
+            .on::<lsp_types::request::GotoDeclaration>(handlers::handle_goto_declaration)
             .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)
             .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
             .on::<lsp_types::request::Completion>(handlers::handle_completion)
@@ -542,6 +562,7 @@ fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()>
             .on::<lsp_types::request::Rename>(handlers::handle_rename)
             .on::<lsp_types::request::References>(handlers::handle_references)
             .on::<lsp_types::request::Formatting>(handlers::handle_formatting)
+            .on::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting)
             .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)
             .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
             .on::<lsp_types::request::CallHierarchyIncomingCalls>(
@@ -585,56 +606,44 @@ fn on_notification(&mut self, not: Notification) -> Result<()> {
                     if this
                         .mem_docs
                         .insert(path.clone(), DocumentData::new(params.text_document.version))
-                        .is_some()
+                        .is_err()
                     {
                         log::error!("duplicate DidOpenTextDocument: {}", path)
                     }
-                    let changed = this
-                        .vfs
+                    this.vfs
                         .write()
                         .0
                         .set_file_contents(path, Some(params.text_document.text.into_bytes()));
-
-                    // If the VFS contents are unchanged, update diagnostics, since `handle_event`
-                    // won't see any changes. This avoids missing diagnostics when opening a file.
-                    //
-                    // If the file *was* changed, `handle_event` will already recompute and send
-                    // diagnostics. We can't do it here, since the *current* file contents might be
-                    // unset in salsa, since the VFS change hasn't been applied to the database yet.
-                    if !changed {
-                        this.maybe_update_diagnostics();
-                    }
                 }
                 Ok(())
             })?
             .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| {
                 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
-                    let doc = match this.mem_docs.get_mut(&path) {
-                        Some(doc) => doc,
+                    match this.mem_docs.get_mut(&path) {
+                        Some(doc) => {
+                            // The version passed in DidChangeTextDocument is the version after all edits are applied
+                            // so we should apply it before the vfs is notified.
+                            doc.version = params.text_document.version;
+                        }
                         None => {
                             log::error!("expected DidChangeTextDocument: {}", path);
                             return Ok(());
                         }
                     };
+
                     let vfs = &mut this.vfs.write().0;
                     let file_id = vfs.file_id(&path).unwrap();
                     let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
                     apply_document_changes(&mut text, params.content_changes);
 
-                    // The version passed in DidChangeTextDocument is the version after all edits are applied
-                    // so we should apply it before the vfs is notified.
-                    doc.version = params.text_document.version;
-
                     vfs.set_file_contents(path.clone(), Some(text.into_bytes()));
                 }
                 Ok(())
             })?
             .on::<lsp_types::notification::DidCloseTextDocument>(|this, params| {
-                let mut version = None;
                 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
-                    match this.mem_docs.remove(&path) {
-                        Some(doc) => version = Some(doc.version),
-                        None => log::error!("orphan DidCloseTextDocument: {}", path),
+                    if this.mem_docs.remove(&path).is_err() {
+                        log::error!("orphan DidCloseTextDocument: {}", path);
                     }
 
                     this.semantic_tokens_cache.lock().remove(&params.text_document.uri);
@@ -643,17 +652,6 @@ fn on_notification(&mut self, not: Notification) -> Result<()> {
                         this.loader.handle.invalidate(path.to_path_buf());
                     }
                 }
-
-                // Clear the diagnostics for the previously known version of the file.
-                // This prevents stale "cargo check" diagnostics if the file is
-                // closed, "cargo check" is run and then the file is reopened.
-                this.send_notification::<lsp_types::notification::PublishDiagnostics>(
-                    lsp_types::PublishDiagnosticsParams {
-                        uri: params.text_document.uri,
-                        diagnostics: Vec::new(),
-                        version,
-                    },
-                );
                 Ok(())
             })?
             .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
@@ -677,7 +675,7 @@ fn on_notification(&mut self, not: Notification) -> Result<()> {
                     },
                     |this, resp| {
                         log::debug!("config update response: '{:?}", resp);
-                        let Response { error, result, .. } = resp;
+                        let lsp_server::Response { error, result, .. } = resp;
 
                         match (error, result) {
                             (Some(err), _) => {
@@ -699,7 +697,7 @@ fn on_notification(&mut self, not: Notification) -> Result<()> {
                     },
                 );
 
-                return Ok(());
+                Ok(())
             })?
             .on::<lsp_types::notification::DidChangeWatchedFiles>(|this, params| {
                 for change in params.changes {
@@ -712,55 +710,33 @@ fn on_notification(&mut self, not: Notification) -> Result<()> {
             .finish();
         Ok(())
     }
-    fn update_file_notifications_on_threadpool(&mut self) {
-        self.maybe_update_diagnostics();
-
-        // Ensure that only one cache priming task can run at a time
-        self.prime_caches_queue.request_op(());
-        if self.prime_caches_queue.should_start_op().is_none() {
-            return;
-        }
 
-        self.task_pool.handle.spawn_with_sender({
-            let snap = self.snapshot();
-            move |sender| {
-                let cb = |progress| {
-                    sender.send(Task::PrimeCaches(progress)).unwrap();
-                };
-                match snap.analysis.prime_caches(cb) {
-                    Ok(()) => (),
-                    Err(_canceled) => (),
-                }
-            }
-        });
-    }
-    fn maybe_update_diagnostics(&mut self) {
+    fn update_diagnostics(&mut self) {
         let subscriptions = self
             .mem_docs
-            .keys()
-            .map(|path| self.vfs.read().0.file_id(&path).unwrap())
+            .iter()
+            .map(|path| self.vfs.read().0.file_id(path).unwrap())
             .collect::<Vec<_>>();
 
         log::trace!("updating notifications for {:?}", subscriptions);
-        if self.config.publish_diagnostics() {
-            let snapshot = self.snapshot();
-            self.task_pool.handle.spawn(move || {
-                let diagnostics = subscriptions
-                    .into_iter()
-                    .filter_map(|file_id| {
-                        handlers::publish_diagnostics(&snapshot, file_id)
-                            .map_err(|err| {
-                                if !is_canceled(&*err) {
-                                    log::error!("failed to compute diagnostics: {:?}", err);
-                                }
-                                ()
-                            })
-                            .ok()
-                            .map(|diags| (file_id, diags))
-                    })
-                    .collect::<Vec<_>>();
-                Task::Diagnostics(diagnostics)
-            })
-        }
+
+        let snapshot = self.snapshot();
+        self.task_pool.handle.spawn(move || {
+            let diagnostics = subscriptions
+                .into_iter()
+                .filter_map(|file_id| {
+                    handlers::publish_diagnostics(&snapshot, file_id)
+                        .map_err(|err| {
+                            if !is_cancelled(&*err) {
+                                log::error!("failed to compute diagnostics: {:?}", err);
+                            }
+                            ()
+                        })
+                        .ok()
+                        .map(|diags| (file_id, diags))
+                })
+                .collect::<Vec<_>>();
+            Task::Diagnostics(diagnostics)
+        })
     }
 }