From be657377a2d4aba8841b432aa8d95635430b29ef Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 3 Sep 2024 14:14:07 -0600 Subject: [PATCH] Make LspStore more responsible (#17318) It now handles more of the buffer language work that project used to have to. Release Notes: - N/A --- crates/project/src/lsp_store.rs | 215 ++++++++++++++++++++++++++++++- crates/project/src/project.rs | 220 ++++---------------------------- 2 files changed, 241 insertions(+), 194 deletions(-) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 0fde0ac5ad..d61806f54a 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -1,5 +1,5 @@ use crate::{ - buffer_store::BufferStore, + buffer_store::{BufferStore, BufferStoreEvent}, environment::ProjectEnvironment, lsp_command::{self, *}, lsp_ext_command, @@ -108,6 +108,7 @@ pub struct LspStore { HashMap>>, active_entry: Option, _maintain_workspace_config: Task>, + _maintain_buffer_languages: Task<()>, next_diagnostic_group_id: usize, diagnostic_summaries: HashMap, HashMap>>, @@ -134,6 +135,10 @@ pub enum LspStoreEvent { }, LanguageServerLog(LanguageServerId, LanguageServerLogType, String), LanguageServerPrompt(LanguageServerPromptRequest), + LanguageDetected { + buffer: Model, + new_language: Option>, + }, Notification(String), RefreshInlayHints, DiagnosticsUpdated { @@ -218,6 +223,8 @@ impl LspStore { cx: &mut ModelContext, ) -> Self { let yarn = YarnPathStore::new(fs.clone(), cx); + cx.subscribe(&buffer_store, Self::on_buffer_store_event) + .detach(); Self { downstream_client, @@ -227,7 +234,7 @@ impl LspStore { project_id: remote_id.unwrap_or(0), buffer_store, worktree_store, - languages, + languages: languages.clone(), environment, nonce: StdRng::from_entropy().gen(), buffer_snapshots: Default::default(), @@ -244,10 +251,214 @@ impl LspStore { active_entry: None, yarn, _maintain_workspace_config: Self::maintain_workspace_config(cx), + _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx), _subscription: cx.on_app_quit(Self::shutdown_language_servers), } } + fn on_buffer_store_event( + &mut self, + _: Model, + event: &BufferStoreEvent, + cx: &mut ModelContext, + ) { + match event { + BufferStoreEvent::BufferAdded(buffer) => { + self.register_buffer(buffer, cx).log_err(); + } + BufferStoreEvent::BufferChangedFilePath { buffer, old_file } => { + if let Some(old_file) = File::from_dyn(old_file.as_ref()) { + self.unregister_buffer_from_language_servers(&buffer, old_file, cx); + } + + self.detect_language_for_buffer(&buffer, cx); + self.register_buffer_with_language_servers(&buffer, cx); + } + BufferStoreEvent::BufferDropped(_) => {} + } + } + + fn on_buffer_event( + &mut self, + buffer: Model, + event: &language::Event, + cx: &mut ModelContext, + ) { + match event { + language::Event::Edited { .. } => { + self.on_buffer_edited(buffer, cx); + } + + language::Event::Saved => { + self.on_buffer_saved(buffer, cx); + } + + _ => {} + } + } + + fn register_buffer( + &mut self, + buffer: &Model, + cx: &mut ModelContext, + ) -> Result<()> { + buffer.update(cx, |buffer, _| { + buffer.set_language_registry(self.languages.clone()) + }); + + cx.subscribe(buffer, |this, buffer, event, cx| { + this.on_buffer_event(buffer, event, cx); + }) + .detach(); + + self.detect_language_for_buffer(buffer, cx); + self.register_buffer_with_language_servers(buffer, cx); + cx.observe_release(buffer, |this, buffer, cx| { + if let Some(file) = File::from_dyn(buffer.file()) { + if file.is_local() { + let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + for server in this.language_servers_for_buffer(buffer, cx) { + server + .1 + .notify::( + lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new(uri.clone()), + }, + ) + .log_err(); + } + } + } + }) + .detach(); + + Ok(()) + } + + fn maintain_buffer_languages( + languages: Arc, + cx: &mut ModelContext, + ) -> Task<()> { + let mut subscription = languages.subscribe(); + let mut prev_reload_count = languages.reload_count(); + cx.spawn(move |this, mut cx| async move { + while let Some(()) = subscription.next().await { + if let Some(this) = this.upgrade() { + // If the language registry has been reloaded, then remove and + // re-assign the languages on all open buffers. + let reload_count = languages.reload_count(); + if reload_count > prev_reload_count { + prev_reload_count = reload_count; + this.update(&mut cx, |this, cx| { + this.buffer_store.clone().update(cx, |buffer_store, cx| { + for buffer in buffer_store.buffers() { + if let Some(f) = File::from_dyn(buffer.read(cx).file()).cloned() + { + this.unregister_buffer_from_language_servers( + &buffer, &f, cx, + ); + buffer + .update(cx, |buffer, cx| buffer.set_language(None, cx)); + } + } + }); + }) + .ok(); + } + + this.update(&mut cx, |this, cx| { + let mut plain_text_buffers = Vec::new(); + let mut buffers_with_unknown_injections = Vec::new(); + for handle in this.buffer_store.read(cx).buffers() { + let buffer = handle.read(cx); + if buffer.language().is_none() + || buffer.language() == Some(&*language::PLAIN_TEXT) + { + plain_text_buffers.push(handle); + } else if buffer.contains_unknown_injections() { + buffers_with_unknown_injections.push(handle); + } + } + + for buffer in plain_text_buffers { + this.detect_language_for_buffer(&buffer, cx); + this.register_buffer_with_language_servers(&buffer, cx); + } + + for buffer in buffers_with_unknown_injections { + buffer.update(cx, |buffer, cx| buffer.reparse(cx)); + } + }) + .ok(); + } + } + }) + } + + fn detect_language_for_buffer( + &mut self, + buffer_handle: &Model, + cx: &mut ModelContext, + ) { + // If the buffer has a language, set it and start the language server if we haven't already. + let buffer = buffer_handle.read(cx); + let Some(file) = buffer.file() else { + return; + }; + let content = buffer.as_rope(); + let Some(new_language_result) = self + .languages + .language_for_file(file, Some(content), cx) + .now_or_never() + else { + return; + }; + + match new_language_result { + Err(e) => { + if e.is::() { + cx.emit(LspStoreEvent::LanguageDetected { + buffer: buffer_handle.clone(), + new_language: None, + }); + } + } + Ok(new_language) => { + self.set_language_for_buffer(buffer_handle, new_language, cx); + } + }; + } + + pub fn set_language_for_buffer( + &mut self, + buffer: &Model, + new_language: Arc, + cx: &mut ModelContext, + ) { + buffer.update(cx, |buffer, cx| { + if buffer.language().map_or(true, |old_language| { + !Arc::ptr_eq(old_language, &new_language) + }) { + buffer.set_language(Some(new_language.clone()), cx); + } + }); + + let buffer_file = buffer.read(cx).file().cloned(); + let buffer_file = File::from_dyn(buffer_file.as_ref()); + + if let Some(file) = buffer_file { + let worktree = file.worktree.clone(); + if worktree.read(cx).is_local() { + self.start_language_servers(&worktree, new_language.clone(), cx) + } + } + + cx.emit(LspStoreEvent::LanguageDetected { + buffer: buffer.clone(), + new_language: Some(new_language), + }) + } + pub fn buffer_store(&self) -> Model { self.buffer_store.clone() } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 99ebb8ca42..e67cf3e6f8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -52,7 +52,7 @@ use language::{ }, Buffer, CachedLspAdapter, Capability, CodeLabel, ContextProvider, DiagnosticEntry, Diff, Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, - LocalFile, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, + PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, }; use lsp::{CompletionContext, DocumentHighlightKind, LanguageServer, LanguageServerId}; use lsp_command::*; @@ -161,7 +161,6 @@ pub struct Project { buffers_needing_diff: HashSet>, git_diff_debouncer: DebouncedDelay, remotely_created_buffers: Arc>, - _maintain_buffer_languages: Task<()>, terminals: Terminals, node: Option>, default_prettier: DefaultPrettier, @@ -661,7 +660,6 @@ impl Project { cx.observe_global::(Self::on_settings_changed), cx.on_release(Self::release), ], - _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx), active_entry: None, snippets, languages, @@ -847,7 +845,6 @@ impl Project { active_entry: None, collaborators: Default::default(), join_project_response_message_id: response.message_id, - _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx), languages, user_store: user_store.clone(), snippets, @@ -1869,60 +1866,15 @@ impl Project { } self.request_buffer_diff_recalculation(buffer, cx); - buffer.update(cx, |buffer, _| { - buffer.set_language_registry(self.languages.clone()) - }); cx.subscribe(buffer, |this, buffer, event, cx| { this.on_buffer_event(buffer, event, cx); }) .detach(); - self.detect_language_for_buffer(buffer, cx); - self.register_buffer_with_language_servers(buffer, cx); - cx.observe_release(buffer, |this, buffer, cx| { - if let Some(file) = File::from_dyn(buffer.file()) { - if file.is_local() { - let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); - for server in this.language_servers_for_buffer(buffer, cx) { - server - .1 - .notify::( - lsp::DidCloseTextDocumentParams { - text_document: lsp::TextDocumentIdentifier::new(uri.clone()), - }, - ) - .log_err(); - } - } - } - }) - .detach(); - Ok(()) } - fn register_buffer_with_language_servers( - &mut self, - buffer_handle: &Model, - cx: &mut ModelContext, - ) { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.register_buffer_with_language_servers(buffer_handle, cx) - }) - } - - fn unregister_buffer_from_language_servers( - &mut self, - buffer: &Model, - old_file: &File, - cx: &mut AppContext, - ) { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.unregister_buffer_from_language_servers(buffer, old_file, cx) - }) - } - async fn send_buffer_ordered_messages( this: WeakModel, rx: UnboundedReceiver, @@ -2041,14 +1993,7 @@ impl Project { BufferStoreEvent::BufferAdded(buffer) => { self.register_buffer(buffer, cx).log_err(); } - BufferStoreEvent::BufferChangedFilePath { buffer, old_file } => { - if let Some(old_file) = File::from_dyn(old_file.as_ref()) { - self.unregister_buffer_from_language_servers(&buffer, old_file, cx); - } - - self.detect_language_for_buffer(&buffer, cx); - self.register_buffer_with_language_servers(&buffer, cx); - } + BufferStoreEvent::BufferChangedFilePath { .. } => {} BufferStoreEvent::BufferDropped(buffer_id) => { if let Some(ref ssh_session) = self.ssh_session { ssh_session @@ -2085,6 +2030,29 @@ impl Project { LspStoreEvent::LanguageServerLog(server_id, log_type, string) => cx.emit( Event::LanguageServerLog(*server_id, log_type.clone(), string.clone()), ), + LspStoreEvent::LanguageDetected { + buffer, + new_language, + } => { + let Some(new_language) = new_language else { + cx.emit(Event::LanguageNotFound(buffer.clone())); + return; + }; + let buffer_file = buffer.read(cx).file().cloned(); + let settings = + language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); + let buffer_file = File::from_dyn(buffer_file.as_ref()); + let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); + if let Some(prettier_plugins) = + prettier_support::prettier_plugins_for_language(&settings) + { + self.install_default_prettier( + worktree, + prettier_plugins.iter().map(|s| Arc::from(s.as_str())), + cx, + ); + }; + } LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints), LspStoreEvent::LanguageServerPrompt(prompt) => { cx.emit(Event::LanguageServerPrompt(prompt.clone())) @@ -2326,19 +2294,6 @@ impl Project { } } - BufferEvent::Edited { .. } => { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.on_buffer_edited(buffer, cx); - }); - } - - // NEXT STEP have the lsp_store register for these things! - BufferEvent::Saved => { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.on_buffer_saved(buffer, cx); - }); - } - _ => {} } @@ -2412,134 +2367,15 @@ impl Project { }) } - fn maintain_buffer_languages( - languages: Arc, - cx: &mut ModelContext, - ) -> Task<()> { - let mut subscription = languages.subscribe(); - let mut prev_reload_count = languages.reload_count(); - cx.spawn(move |project, mut cx| async move { - while let Some(()) = subscription.next().await { - if let Some(project) = project.upgrade() { - // If the language registry has been reloaded, then remove and - // re-assign the languages on all open buffers. - let reload_count = languages.reload_count(); - if reload_count > prev_reload_count { - prev_reload_count = reload_count; - project - .update(&mut cx, |this, cx| { - this.buffer_store.clone().update(cx, |buffer_store, cx| { - for buffer in buffer_store.buffers() { - if let Some(f) = - File::from_dyn(buffer.read(cx).file()).cloned() - { - this.unregister_buffer_from_language_servers( - &buffer, &f, cx, - ); - buffer.update(cx, |buffer, cx| { - buffer.set_language(None, cx) - }); - } - } - }); - }) - .ok(); - } - - project - .update(&mut cx, |project, cx| { - let mut plain_text_buffers = Vec::new(); - let mut buffers_with_unknown_injections = Vec::new(); - for handle in project.buffer_store.read(cx).buffers() { - let buffer = handle.read(cx); - if buffer.language().is_none() - || buffer.language() == Some(&*language::PLAIN_TEXT) - { - plain_text_buffers.push(handle); - } else if buffer.contains_unknown_injections() { - buffers_with_unknown_injections.push(handle); - } - } - - for buffer in plain_text_buffers { - project.detect_language_for_buffer(&buffer, cx); - project.register_buffer_with_language_servers(&buffer, cx); - } - - for buffer in buffers_with_unknown_injections { - buffer.update(cx, |buffer, cx| buffer.reparse(cx)); - } - }) - .ok(); - } - } - }) - } - - fn detect_language_for_buffer( - &mut self, - buffer_handle: &Model, - cx: &mut ModelContext, - ) { - // If the buffer has a language, set it and start the language server if we haven't already. - let buffer = buffer_handle.read(cx); - let Some(file) = buffer.file() else { - return; - }; - let content = buffer.as_rope(); - let Some(new_language_result) = self - .languages - .language_for_file(file, Some(content), cx) - .now_or_never() - else { - return; - }; - - match new_language_result { - Err(e) => { - if e.is::() { - cx.emit(Event::LanguageNotFound(buffer_handle.clone())) - } - } - Ok(new_language) => { - self.set_language_for_buffer(buffer_handle, new_language, cx); - } - }; - } - pub fn set_language_for_buffer( &mut self, buffer: &Model, new_language: Arc, cx: &mut ModelContext, ) { - buffer.update(cx, |buffer, cx| { - if buffer.language().map_or(true, |old_language| { - !Arc::ptr_eq(old_language, &new_language) - }) { - buffer.set_language(Some(new_language.clone()), cx); - } - }); - - let buffer_file = buffer.read(cx).file().cloned(); - let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); - let buffer_file = File::from_dyn(buffer_file.as_ref()); - let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); - if let Some(prettier_plugins) = prettier_support::prettier_plugins_for_language(&settings) { - self.install_default_prettier( - worktree, - prettier_plugins.iter().map(|s| Arc::from(s.as_str())), - cx, - ); - }; - if let Some(file) = buffer_file { - let worktree = file.worktree.clone(); - if worktree.read(cx).is_local() { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.start_language_servers(&worktree, new_language, cx); - }); - } - } + self.lsp_store.update(cx, |lsp_store, cx| { + lsp_store.set_language_for_buffer(buffer, new_language, cx) + }) } pub fn restart_language_servers_for_buffers(