diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 3ba61b8dca..290b8e6d50 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -194,7 +194,7 @@ impl ItemView for Editor { .clone(); project.update(cx, |project, cx| { - project.save_buffer_as(buffer, &abs_path, cx) + project.save_buffer_as(buffer, abs_path, cx) }) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ba79a2fdfc..4a30468504 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -11,14 +11,14 @@ use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; -use language::{Buffer, DiagnosticEntry, LanguageRegistry}; -use lsp::DiagnosticSeverity; +use language::{Buffer, DiagnosticEntry, Language, LanguageRegistry}; +use lsp::{DiagnosticSeverity, LanguageServer}; use postage::{prelude::Stream, watch}; use std::{ path::{Path, PathBuf}, sync::{atomic::AtomicBool, Arc}, }; -use util::TryFutureExt as _; +use util::{ResultExt, TryFutureExt as _}; pub use fs::*; pub use worktree::*; @@ -27,6 +27,7 @@ pub struct Project { worktrees: Vec>, active_entry: Option, languages: Arc, + language_servers: HashMap<(Arc, String), Arc>, client: Arc, user_store: ModelHandle, fs: Arc, @@ -62,7 +63,6 @@ pub enum Event { ActiveEntryChanged(Option), WorktreeRemoved(WorktreeId), DiskBasedDiagnosticsStarted, - DiskBasedDiagnosticsUpdated { worktree_id: WorktreeId }, DiskBasedDiagnosticsFinished, DiagnosticsUpdated(ProjectPath), } @@ -191,6 +191,7 @@ impl Project { user_store, fs, pending_disk_based_diagnostics: 0, + language_servers: Default::default(), } }) } @@ -283,6 +284,7 @@ impl Project { replica_id, }, pending_disk_based_diagnostics: 0, + language_servers: Default::default(), }; for worktree in worktrees { this.add_worktree(worktree, cx); @@ -457,12 +459,40 @@ impl Project { } pub fn open_buffer( - &self, + &mut self, path: ProjectPath, cx: &mut ModelContext, ) -> Task>> { if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) { - worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx)) + let language_server = worktree + .read(cx) + .as_local() + .map(|w| w.abs_path().clone()) + .and_then(|worktree_abs_path| { + let abs_path = worktree.abs_path().join(&path.path); + let language = self.languages.select_language(&abs_path).cloned(); + if let Some(language) = language { + if let Some(language_server) = + self.register_language_server(worktree.abs_path(), &language, cx) + { + worktree.register_language(&language, &language_server); + } + } + }); + worktree.update(cx, |worktree, cx| { + if let Some(worktree) = worktree.as_local_mut() { + let abs_path = worktree.abs_path().join(&path.path); + let language = self.languages.select_language(&abs_path).cloned(); + if let Some(language) = language { + if let Some(language_server) = + self.register_language_server(worktree.abs_path(), &language, cx) + { + worktree.register_language(&language, &language_server); + } + } + } + worktree.open_buffer(path.path, cx) + }) } else { cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) }) } @@ -471,24 +501,194 @@ impl Project { pub fn save_buffer_as( &self, buffer: ModelHandle, - abs_path: &Path, + abs_path: PathBuf, cx: &mut ModelContext, ) -> Task> { - let result = self.worktree_for_abs_path(abs_path, cx); - cx.spawn(|_, mut cx| async move { + let result = self.worktree_for_abs_path(&abs_path, cx); + let languages = self.languages.clone(); + cx.spawn(|this, mut cx| async move { let (worktree, path) = result.await?; worktree .update(&mut cx, |worktree, cx| { - worktree - .as_local() - .unwrap() - .save_buffer_as(buffer.clone(), path, cx) + let worktree = worktree.as_local_mut().unwrap(); + let language = languages.select_language(&abs_path); + if let Some(language) = language { + if let Some(language_server) = this.update(cx, |this, cx| { + this.register_language_server(worktree.abs_path(), language, cx) + }) { + worktree.register_language(&language, &language_server); + } + } + + worktree.save_buffer_as(buffer.clone(), path, cx) }) .await?; Ok(()) }) } + fn register_language_server( + &mut self, + worktree_abs_path: Arc, + language: &Arc, + cx: &mut ModelContext, + ) -> Option> { + if let Some(server) = self + .language_servers + .get(&(worktree_abs_path.clone(), language.name().to_string())) + { + return Some(server.clone()); + } + + if let Some(language_server) = language + .start_server(&worktree_abs_path, cx) + .log_err() + .flatten() + { + enum DiagnosticProgress { + Updating, + Updated, + } + + let disk_based_sources = language + .disk_based_diagnostic_sources() + .cloned() + .unwrap_or_default(); + let disk_based_diagnostics_progress_token = + language.disk_based_diagnostics_progress_token().cloned(); + let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); + let (disk_based_diagnostics_done_tx, disk_based_diagnostics_done_rx) = + smol::channel::unbounded(); + language_server + .on_notification::(move |params| { + smol::block_on(diagnostics_tx.send(params)).ok(); + }) + .detach(); + cx.spawn_weak(|this, mut cx| { + let has_disk_based_diagnostic_progress_token = + disk_based_diagnostics_progress_token.is_some(); + let disk_based_diagnostics_done_tx = disk_based_diagnostics_done_tx.clone(); + async move { + while let Ok(diagnostics) = diagnostics_rx.recv().await { + if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { + handle.update(&mut cx, |this, cx| { + if !has_disk_based_diagnostic_progress_token { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updating), + ) + .ok(); + } + this.update_diagnostics(diagnostics, &disk_based_sources, cx) + .log_err(); + if !has_disk_based_diagnostic_progress_token { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updated), + ) + .ok(); + } + }) + } else { + break; + } + } + } + }) + .detach(); + + let mut pending_disk_based_diagnostics: i32 = 0; + language_server + .on_notification::(move |params| { + let token = match params.token { + lsp::NumberOrString::Number(_) => None, + lsp::NumberOrString::String(token) => Some(token), + }; + + if token == disk_based_diagnostics_progress_token { + match params.value { + lsp::ProgressParamsValue::WorkDone(progress) => match progress { + lsp::WorkDoneProgress::Begin(_) => { + if pending_disk_based_diagnostics == 0 { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updating), + ) + .ok(); + } + pending_disk_based_diagnostics += 1; + } + lsp::WorkDoneProgress::End(_) => { + pending_disk_based_diagnostics -= 1; + if pending_disk_based_diagnostics == 0 { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updated), + ) + .ok(); + } + } + _ => {} + }, + } + } + }) + .detach(); + let rpc = self.client.clone(); + cx.spawn_weak(|this, mut cx| async move { + while let Ok(progress) = disk_based_diagnostics_done_rx.recv().await { + if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { + match progress { + DiagnosticProgress::Updating => { + let message = handle.update(&mut cx, |this, cx| { + cx.emit(Event::DiskBasedDiagnosticsUpdating); + let this = this.as_local().unwrap(); + this.share.as_ref().map(|share| { + proto::DiskBasedDiagnosticsUpdating { + project_id: share.project_id, + worktree_id: this.id().to_proto(), + } + }) + }); + + if let Some(message) = message { + rpc.send(message).await.log_err(); + } + } + DiagnosticProgress::Updated => { + let message = handle.update(&mut cx, |this, cx| { + cx.emit(Event::DiskBasedDiagnosticsUpdated); + let this = this.as_local().unwrap(); + this.share.as_ref().map(|share| { + proto::DiskBasedDiagnosticsUpdated { + project_id: share.project_id, + worktree_id: this.id().to_proto(), + } + }) + }); + + if let Some(message) = message { + rpc.send(message).await.log_err(); + } + } + } + } else { + break; + } + } + }) + .detach(); + + self.language_servers.insert( + (worktree_abs_path.clone(), language.name().to_string()), + language_server.clone(), + ); + Some(language_server) + } else { + None + } + } + pub fn worktree_for_abs_path( &self, abs_path: &Path, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 192c3b2fca..06b61b7175 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1156,157 +1156,10 @@ impl LocalWorktree { pub fn register_language( &mut self, language: &Arc, - cx: &mut ModelContext, - ) -> Option> { - if let Some(server) = self.language_servers.get(language.name()) { - return Some(server.clone()); - } - - if let Some(language_server) = language - .start_server(self.abs_path(), cx) - .log_err() - .flatten() - { - enum DiagnosticProgress { - Updating, - Updated, - } - - let disk_based_sources = language - .disk_based_diagnostic_sources() - .cloned() - .unwrap_or_default(); - let disk_based_diagnostics_progress_token = - language.disk_based_diagnostics_progress_token().cloned(); - let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); - let (disk_based_diagnostics_done_tx, disk_based_diagnostics_done_rx) = - smol::channel::unbounded(); - language_server - .on_notification::(move |params| { - smol::block_on(diagnostics_tx.send(params)).ok(); - }) - .detach(); - cx.spawn_weak(|this, mut cx| { - let has_disk_based_diagnostic_progress_token = - disk_based_diagnostics_progress_token.is_some(); - let disk_based_diagnostics_done_tx = disk_based_diagnostics_done_tx.clone(); - async move { - while let Ok(diagnostics) = diagnostics_rx.recv().await { - if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - handle.update(&mut cx, |this, cx| { - if !has_disk_based_diagnostic_progress_token { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updating), - ) - .ok(); - } - this.update_diagnostics(diagnostics, &disk_based_sources, cx) - .log_err(); - if !has_disk_based_diagnostic_progress_token { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updated), - ) - .ok(); - } - }) - } else { - break; - } - } - } - }) - .detach(); - - let mut pending_disk_based_diagnostics: i32 = 0; - language_server - .on_notification::(move |params| { - let token = match params.token { - lsp::NumberOrString::Number(_) => None, - lsp::NumberOrString::String(token) => Some(token), - }; - - if token == disk_based_diagnostics_progress_token { - match params.value { - lsp::ProgressParamsValue::WorkDone(progress) => match progress { - lsp::WorkDoneProgress::Begin(_) => { - if pending_disk_based_diagnostics == 0 { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updating), - ) - .ok(); - } - pending_disk_based_diagnostics += 1; - } - lsp::WorkDoneProgress::End(_) => { - pending_disk_based_diagnostics -= 1; - if pending_disk_based_diagnostics == 0 { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updated), - ) - .ok(); - } - } - _ => {} - }, - } - } - }) - .detach(); - let rpc = self.client.clone(); - cx.spawn_weak(|this, mut cx| async move { - while let Ok(progress) = disk_based_diagnostics_done_rx.recv().await { - if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - match progress { - DiagnosticProgress::Updating => { - let message = handle.update(&mut cx, |this, cx| { - cx.emit(Event::DiskBasedDiagnosticsUpdating); - let this = this.as_local().unwrap(); - this.share.as_ref().map(|share| { - proto::DiskBasedDiagnosticsUpdating { - project_id: share.project_id, - worktree_id: this.id().to_proto(), - } - }) - }); - - if let Some(message) = message { - rpc.send(message).await.log_err(); - } - } - DiagnosticProgress::Updated => { - let message = handle.update(&mut cx, |this, cx| { - cx.emit(Event::DiskBasedDiagnosticsUpdated); - let this = this.as_local().unwrap(); - this.share.as_ref().map(|share| { - proto::DiskBasedDiagnosticsUpdated { - project_id: share.project_id, - worktree_id: this.id().to_proto(), - } - }) - }); - - if let Some(message) = message { - rpc.send(message).await.log_err(); - } - } - } - } else { - break; - } - } - }) - .detach(); - - self.language_servers - .insert(language.name().to_string(), language_server.clone()); - Some(language_server.clone()) - } else { - None - } + language_server: &Arc, + ) { + self.language_servers + .insert(language.name().to_string(), language_server.clone()); } fn get_open_buffer( @@ -1342,7 +1195,7 @@ impl LocalWorktree { .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx)) .await?; - let (diagnostics, language, language_server) = this.update(&mut cx, |this, cx| { + let (diagnostics, language, language_server) = this.update(&mut cx, |this, _| { let this = this.as_local_mut().unwrap(); let diagnostics = this.diagnostics.get(&path).cloned(); let language = this @@ -1351,7 +1204,7 @@ impl LocalWorktree { .cloned(); let server = language .as_ref() - .and_then(|language| this.register_language(language, cx)); + .and_then(|language| this.language_servers.get(language.name()).cloned()); (diagnostics, language, server) }); @@ -1522,7 +1375,7 @@ impl LocalWorktree { } }); - let (language, language_server) = this.update(&mut cx, |worktree, cx| { + let (language, language_server) = this.update(&mut cx, |worktree, _| { let worktree = worktree.as_local_mut().unwrap(); let language = worktree .language_registry() @@ -1530,7 +1383,7 @@ impl LocalWorktree { .cloned(); let language_server = language .as_ref() - .and_then(|language| worktree.register_language(language, cx)); + .and_then(|language| worktree.language_servers.get(language.name()).cloned()); (language, language_server.clone()) }); @@ -3277,7 +3130,7 @@ mod tests { use client::test::{FakeHttpClient, FakeServer}; use fs::RealFs; use gpui::test::subscribe; - use language::{tree_sitter_rust, DiagnosticEntry, LanguageServerConfig}; + use language::{tree_sitter_rust, DiagnosticEntry, Language, LanguageServerConfig}; use language::{Diagnostic, LanguageConfig}; use lsp::Url; use rand::prelude::*;