diff --git a/lang/driver/src/database.rs b/lang/driver/src/database.rs index bf2369a48e..fe5d17a246 100644 --- a/lang/driver/src/database.rs +++ b/lang/driver/src/database.rs @@ -383,9 +383,8 @@ impl Database { } /// Invalidate the file behind the given URI and all its reverse dependencies - pub async fn invalidate(&mut self, uri: &Url) -> Result<(), Error> { + pub async fn invalidate(&mut self, uri: &Url) { self.invalidate_impl(uri); - self.build_dependency_dag().await?; let rev_deps: HashSet = self.deps.reverse_dependencies(uri).into_iter().cloned().collect(); log::debug!( @@ -396,7 +395,6 @@ impl Database { for rev_dep in &rev_deps { self.invalidate_impl(rev_dep); } - Ok(()) } fn invalidate_impl(&mut self, uri: &Url) { @@ -432,7 +430,7 @@ impl Database { } pub async fn write_source(&mut self, uri: &Url, source: &str) -> Result<(), Error> { - self.invalidate(uri).await?; + self.invalidate(uri).await; self.source.write_string(uri, source).await.map_err(|err| err.into()) } diff --git a/lang/driver/src/fs.rs b/lang/driver/src/fs.rs index 1d715f63a6..02a951e5b1 100644 --- a/lang/driver/src/fs.rs +++ b/lang/driver/src/fs.rs @@ -16,6 +16,11 @@ pub trait FileSource: Send + Sync { fn manage(&mut self, uri: &Url) -> bool; /// Check if the source manages a file with the given URI fn manages(&self, uri: &Url) -> bool; + /// Stop managing a file with the given URI + /// + /// This is mostly relevant for in-memory sources. + /// Returns `true` if the source was managing the file. + fn forget(&mut self, uri: &Url) -> bool; /// Read the contents of a file with the given URI async fn read_to_string(&mut self, uri: &Url) -> Result; /// Write the contents of a file with the given URI @@ -61,6 +66,11 @@ mod file_system { self.root.join(filepath).exists() } + fn forget(&mut self, uri: &Url) -> bool { + let filepath = uri.to_file_path().expect("Failed to convert URI to filepath"); + self.root.join(filepath).exists() + } + async fn read_to_string(&mut self, uri: &Url) -> Result { let filepath = uri.to_file_path().expect("Failed to convert URI to filepath"); let path = self.root.join(filepath); @@ -116,6 +126,12 @@ impl FileSource for InMemorySource { self.files.contains_key(uri) } + fn forget(&mut self, uri: &Url) -> bool { + let managed = self.files.remove(uri).is_some(); + self.modified.remove(uri); + managed + } + async fn read_to_string(&mut self, uri: &Url) -> Result { if self.manages(uri) { self.modified.insert(uri.clone(), false); @@ -158,6 +174,10 @@ where self.first.manages(uri) || self.second.manages(uri) } + fn forget(&mut self, uri: &Url) -> bool { + self.first.forget(uri) || self.second.forget(uri) + } + async fn read_to_string(&mut self, uri: &Url) -> Result { match self.first.read_to_string(uri).await { Ok(source) => Ok(source), diff --git a/lang/lsp/src/diagnostics.rs b/lang/lsp/src/diagnostics.rs index e6dc767376..18f4917cf3 100644 --- a/lang/lsp/src/diagnostics.rs +++ b/lang/lsp/src/diagnostics.rs @@ -11,17 +11,38 @@ use miette_util::FromMiette; use crate::conversion::ToLsp; pub trait Diagnostics { - fn diagnostics(&self, uri: &Url, result: Result<(), Error>) -> Vec { - match result { - Ok(()) => vec![], - Err(err) => self.error_diagnostics(uri, err), - } - } + /// Compute the diagnostics for the given URI and all of its reverse dependencies. + async fn diagnostics(&mut self, uri: &Url, result: Result<(), Error>) -> DiagnosticsPerUri; fn error_diagnostics(&self, uri: &Url, error: Error) -> Vec; } +pub type DiagnosticsPerUri = ast::HashMap>; + impl Diagnostics for Database { + async fn diagnostics(&mut self, uri: &Url, result: Result<(), Error>) -> DiagnosticsPerUri { + // When computing the diagnostics for an URI, we also need to recompute the diagnostics for all of its reverse dependencies. + let rev_deps: Vec<_> = self.deps.reverse_dependencies(uri).into_iter().cloned().collect(); + let mut diagnostics = ast::HashMap::default(); + + for uri in rev_deps { + let mut diagnostics_for_uri = vec![]; + let ast = self.ast(&uri).await; + if let Err(err) = ast { + diagnostics_for_uri.extend(self.error_diagnostics(&uri, err)); + } + diagnostics.insert(uri, diagnostics_for_uri); + } + + if let Err(err) = result { + diagnostics.insert(uri.clone(), self.error_diagnostics(uri, err)); + } else { + diagnostics.insert(uri.clone(), vec![]); + } + + diagnostics + } + fn error_diagnostics(&self, uri: &Url, error: Error) -> Vec { // Compute the range where the error should be displayed. // The range is computed from the first available label, otherwise diff --git a/lang/lsp/src/server.rs b/lang/lsp/src/server.rs index 6aa3481973..7fa1b17630 100644 --- a/lang/lsp/src/server.rs +++ b/lang/lsp/src/server.rs @@ -6,7 +6,7 @@ use driver::Database; #[cfg(not(target_arch = "wasm32"))] use driver::{FileSource, FileSystemSource, InMemorySource}; -use crate::conversion::FromLsp; +use crate::conversion::{FromLsp, ToLsp}; use super::capabilities::*; use super::diagnostics::*; @@ -74,8 +74,23 @@ impl LanguageServer for Server { source_mut.write_string(&text_document.uri.from_lsp(), &text_document.text).await.unwrap(); let res = db.ast(&text_document.uri.from_lsp()).await.map(|_| ()); - let diags = db.diagnostics(&text_document.uri.from_lsp(), res); - self.send_diagnostics(text_document.uri, diags).await; + let diags = db.diagnostics(&text_document.uri.from_lsp(), res).await; + self.send_diagnostics(diags).await; + } + + async fn did_close(&self, params: DidCloseTextDocumentParams) { + let text_document = params.text_document; + let mut db = self.database.write().await; + + self.client + .log_message( + MessageType::INFO, + format!("Closed file: {}", text_document.uri.from_lsp()), + ) + .await; + + let source_mut = db.file_source_mut(); + source_mut.forget(&text_document.uri.from_lsp()); } async fn did_change(&self, params: DidChangeTextDocumentParams) { @@ -96,15 +111,12 @@ impl LanguageServer for Server { assert!(source_mut.manage(&text_document.uri.from_lsp())); source_mut.write_string(&text_document.uri.from_lsp(), &text).await.unwrap(); - let res = db.invalidate(&text_document.uri.from_lsp()).await; + db.invalidate(&text_document.uri.from_lsp()).await; - let res = match res { - Ok(()) => db.ast(&text_document.uri.from_lsp()).await.map(|_| ()), - Err(err) => Err(err), - }; + let res = db.ast(&text_document.uri.from_lsp()).await.map(|_| ()); + let diags = db.diagnostics(&text_document.uri.from_lsp(), res).await; - let diags = db.diagnostics(&text_document.uri.from_lsp(), res); - self.send_diagnostics(text_document.uri, diags).await; + self.send_diagnostics(diags).await; } async fn goto_definition( @@ -131,7 +143,9 @@ impl LanguageServer for Server { } impl Server { - pub(crate) async fn send_diagnostics(&self, uri: Uri, diags: Vec) { - self.client.publish_diagnostics(uri, diags, None).await; + pub(crate) async fn send_diagnostics(&self, diags: DiagnosticsPerUri) { + for (uri, diags) in diags { + self.client.publish_diagnostics(uri.to_lsp(), diags, None).await; + } } } diff --git a/web/crates/browser/src/source.rs b/web/crates/browser/src/source.rs index 123133c417..b8e27eb8aa 100644 --- a/web/crates/browser/src/source.rs +++ b/web/crates/browser/src/source.rs @@ -19,6 +19,10 @@ impl FileSource for FetchSource { true } + fn forget(&mut self, _uri: &reqwest::Url) -> bool { + true + } + async fn read_to_string(&mut self, uri: &reqwest::Url) -> Result { let url = get_base_url().join(uri.path()).map_err(|_| DriverError::FileNotFound(uri.clone()))?;