diff --git a/Cargo.lock b/Cargo.lock index 1f6edc7606..01caa941e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7398,7 +7398,6 @@ dependencies = [ "serde", "serde_json", "smol", - "text", "util", ] @@ -9835,7 +9834,6 @@ dependencies = [ "log", "lsp", "node_runtime", - "once_cell", "parking_lot", "pathdiff", "paths", diff --git a/Cargo.toml b/Cargo.toml index b958452a02..e7481808e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -372,7 +372,7 @@ async-tungstenite = "0.28" async-watch = "0.3.1" async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] } base64 = "0.22" -bitflags = "2.8.0" +bitflags = "2.6.0" blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" } blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" } blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" } @@ -420,13 +420,12 @@ libc = "0.2" libsqlite3-sys = { version = "0.30.1", features = ["bundled"] } linkify = "0.10.0" livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev="060964da10574cd9bf06463a53bf6e0769c5c45e", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false } -log = { version = "0.4.25", features = ["kv_unstable_serde", "serde"] } +log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } markup5ever_rcdom = "0.3.0" nanoid = "0.4" nbformat = { version = "0.10.0" } nix = "0.29" num-format = "0.4.4" -once_cell = "1.20" ordered-float = "2.1.1" palette = { version = "0.7.5", default-features = false, features = ["std"] } parking_lot = "0.12.1" @@ -509,7 +508,7 @@ tree-sitter = { version = "0.23", features = ["wasm"] } tree-sitter-bash = "0.23" tree-sitter-c = "0.23" tree-sitter-cpp = "0.23" -tree-sitter-css = "0.23.2" +tree-sitter-css = "0.23" tree-sitter-elixir = "0.3" tree-sitter-embedded-template = "0.23.0" tree-sitter-go = "0.23" diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 7437546cba..020a86c9f8 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -53,7 +53,7 @@ reqwest_client.workspace = true rpc.workspace = true rustc-demangle.workspace = true scrypt = "0.11" -sea-orm = { version = "1.1.4", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] } +sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] } semantic_version.workspace = true semver.workspace = true serde.workspace = true @@ -116,7 +116,7 @@ release_channel.workspace = true remote = { workspace = true, features = ["test-support"] } remote_server.workspace = true rpc = { workspace = true, features = ["test-support"] } -sea-orm = { version = "1.1.4", features = ["sqlx-sqlite"] } +sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-sqlite"] } serde_json.workspace = true session = { workspace = true, features = ["test-support"] } settings = { workspace = true, features = ["test-support"] } diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 9ffb5f3fb9..67280765f6 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -461,14 +461,12 @@ impl Copilot { .on_notification::(|_, _| { /* Silence the notification */ }) .detach(); + let initialize_params = None; let configuration = lsp::DidChangeConfigurationParams { settings: Default::default(), }; let server = cx - .update(|cx| { - let params = server.default_initialize_params(cx); - server.initialize(params, configuration.into(), cx) - })? + .update(|cx| server.initialize(initialize_params, configuration.into(), cx))? .await?; let status = server diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ad13de90b6..9efc74c787 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -12476,27 +12476,28 @@ impl Editor { cx.emit(SearchEvent::MatchesInvalidated); if *singleton_buffer_edited { if let Some(project) = &self.project { + let project = project.read(cx); #[allow(clippy::mutable_key_type)] - let languages_affected = multibuffer.update(cx, |multibuffer, cx| { - multibuffer - .all_buffers() - .into_iter() - .filter_map(|buffer| { - buffer.update(cx, |buffer, cx| { - let language = buffer.language()?; - let should_discard = project.update(cx, |project, cx| { - project.is_local() - && project.for_language_servers_for_local_buffer( - buffer, - |it| it.count() == 0, - cx, - ) - }); - should_discard.not().then_some(language.clone()) - }) - }) - .collect::>() - }); + let languages_affected = multibuffer + .read(cx) + .all_buffers() + .into_iter() + .filter_map(|buffer| { + let buffer = buffer.read(cx); + let language = buffer.language()?; + if project.is_local() + && project + .language_servers_for_local_buffer(buffer, cx) + .count() + == 0 + { + None + } else { + Some(language) + } + }) + .cloned() + .collect::>(); if !languages_affected.is_empty() { self.refresh_inlay_hints( InlayHintRefreshReason::BufferEdited(languages_affected), @@ -13050,18 +13051,15 @@ impl Editor { self.handle_input(text, cx); } - pub fn supports_inlay_hints(&self, cx: &mut AppContext) -> bool { + pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool { let Some(provider) = self.semantics_provider.as_ref() else { return false; }; let mut supports = false; - self.buffer().update(cx, |this, cx| { - this.for_each_buffer(|buffer| { - supports |= provider.supports_inlay_hints(buffer, cx); - }) + self.buffer().read(cx).for_each_buffer(|buffer| { + supports |= provider.supports_inlay_hints(buffer, cx); }); - supports } @@ -13673,7 +13671,7 @@ pub trait SemanticsProvider { cx: &mut AppContext, ) -> Option>>; - fn supports_inlay_hints(&self, buffer: &Model, cx: &mut AppContext) -> bool; + fn supports_inlay_hints(&self, buffer: &Model, cx: &AppContext) -> bool; fn document_highlights( &self, @@ -14058,25 +14056,17 @@ impl SemanticsProvider for Model { })) } - fn supports_inlay_hints(&self, buffer: &Model, cx: &mut AppContext) -> bool { + fn supports_inlay_hints(&self, buffer: &Model, cx: &AppContext) -> bool { // TODO: make this work for remote projects - buffer.update(cx, |buffer, cx| { - self.update(cx, |this, cx| { - this.for_language_servers_for_local_buffer( - buffer, - |mut it| { - it.any( - |(_, server)| match server.capabilities().inlay_hint_provider { - Some(lsp::OneOf::Left(enabled)) => enabled, - Some(lsp::OneOf::Right(_)) => true, - None => false, - }, - ) - }, - cx, - ) - }) - }) + self.read(cx) + .language_servers_for_local_buffer(buffer.read(cx), cx) + .any( + |(_, server)| match server.capabilities().inlay_hint_provider { + Some(lsp::OneOf::Left(enabled)) => enabled, + Some(lsp::OneOf::Right(_)) => true, + None => false, + }, + ) } fn inlay_hints( diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index f0a9fd90af..6f789acce7 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -6839,7 +6839,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { let fs = FakeFs::new(cx.executor()); fs.insert_file("/file.rs", Default::default()).await; - let project = Project::test(fs, ["/".as_ref()], cx).await; + let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let language_registry = project.read_with(cx, |project, _| project.languages().clone()); language_registry.add(rust_lang()); @@ -7193,7 +7193,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { let fs = FakeFs::new(cx.executor()); fs.insert_file("/file.rs", Default::default()).await; - let project = Project::test(fs, ["/".as_ref()], cx).await; + let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let language_registry = project.read_with(cx, |project, _| project.languages().clone()); language_registry.add(rust_lang()); @@ -7327,7 +7327,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { let fs = FakeFs::new(cx.executor()); fs.insert_file("/file.rs", Default::default()).await; - let project = Project::test(fs, ["/".as_ref()], cx).await; + let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let language_registry = project.read_with(cx, |project, _| project.languages().clone()); language_registry.add(Arc::new(Language::new( @@ -10742,6 +10742,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test 0, "Should not restart LSP server on an unrelated LSP settings change" ); + update_test_project_settings(cx, |project_settings| { project_settings.lsp.insert( language_server_name.into(), diff --git a/crates/editor/src/lsp_ext.rs b/crates/editor/src/lsp_ext.rs index e4c28243a1..2937e75943 100644 --- a/crates/editor/src/lsp_ext.rs +++ b/crates/editor/src/lsp_ext.rs @@ -11,7 +11,7 @@ use multi_buffer::Anchor; pub(crate) fn find_specific_language_server_in_selection( editor: &Editor, - cx: &mut WindowContext, + cx: &WindowContext, filter_language: F, language_server_name: &str, ) -> Option<(Anchor, Arc, LanguageServerId, Model)> @@ -21,6 +21,7 @@ where let Some(project) = &editor.project else { return None; }; + let multibuffer = editor.buffer().read(cx); let mut language_servers_for = HashMap::default(); editor .selections @@ -28,33 +29,29 @@ where .iter() .filter(|selection| selection.start == selection.end) .filter_map(|selection| Some((selection.start.buffer_id?, selection.start))) - .find_map(|(buffer_id, trigger_anchor)| { - let buffer = editor.buffer().read(cx).buffer(buffer_id)?; + .filter_map(|(buffer_id, trigger_anchor)| { + let buffer = multibuffer.buffer(buffer_id)?; let server_id = *match language_servers_for.entry(buffer_id) { Entry::Occupied(occupied_entry) => occupied_entry.into_mut(), Entry::Vacant(vacant_entry) => { - let language_server_id = buffer.update(cx, |buffer, cx| { - project.update(cx, |project, cx| { - project.for_language_servers_for_local_buffer( - buffer, - |mut it| { - it.find_map(|(adapter, server)| { - if adapter.name.0.as_ref() == language_server_name { - Some(server.server_id()) - } else { - None - } - }) - }, - cx, - ) - }) - }); + let language_server_id = project + .read(cx) + .language_servers_for_local_buffer(buffer.read(cx), cx) + .find_map(|(adapter, server)| { + if adapter.name.0.as_ref() == language_server_name { + Some(server.server_id()) + } else { + None + } + }); vacant_entry.insert(language_server_id) } } .as_ref()?; + Some((buffer, trigger_anchor, server_id)) + }) + .find_map(|(buffer, trigger_anchor, server_id)| { let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?; if !filter_language(&language) { return None; diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index abfda04e7b..923dcc24b9 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -455,7 +455,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider { self.0.resolve_inlay_hint(hint, buffer, server_id, cx) } - fn supports_inlay_hints(&self, buffer: &Model, cx: &mut AppContext) -> bool { + fn supports_inlay_hints(&self, buffer: &Model, cx: &AppContext) -> bool { if let Some(buffer) = self.to_base(&buffer, &[], cx) { self.0.supports_inlay_hints(&buffer, cx) } else { diff --git a/crates/gpui_macros/Cargo.toml b/crates/gpui_macros/Cargo.toml index e605bbb2f2..6936066192 100644 --- a/crates/gpui_macros/Cargo.toml +++ b/crates/gpui_macros/Cargo.toml @@ -14,6 +14,6 @@ proc-macro = true doctest = false [dependencies] -proc-macro2 = "1.0.93" +proc-macro2 = "1.0.66" quote = "1.0.9" syn = { version = "1.0.72", features = ["full", "extra-traits"] } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 2769e0fe0b..8061c7efa9 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -45,6 +45,7 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use settings::WorktreeId; use smol::future::FutureExt as _; +use std::num::NonZeroU32; use std::{ any::Any, ffi::OsStr, @@ -60,7 +61,6 @@ use std::{ Arc, LazyLock, }, }; -use std::{num::NonZeroU32, sync::OnceLock}; use syntax_map::{QueryCursorHandle, SyntaxSnapshot}; use task::RunnableTag; pub use task_context::{ContextProvider, RunnableRange}; @@ -163,7 +163,6 @@ pub struct CachedLspAdapter { pub adapter: Arc, pub reinstall_attempt_count: AtomicU64, cached_binary: futures::lock::Mutex>, - attach_kind: OnceLock, } impl Debug for CachedLspAdapter { @@ -199,7 +198,6 @@ impl CachedLspAdapter { adapter, cached_binary: Default::default(), reinstall_attempt_count: AtomicU64::new(0), - attach_kind: Default::default(), }) } @@ -261,38 +259,6 @@ impl CachedLspAdapter { .cloned() .unwrap_or_else(|| language_name.lsp_id()) } - pub fn find_project_root( - &self, - path: &Path, - ancestor_depth: usize, - delegate: &Arc, - ) -> Option> { - self.adapter - .find_project_root(path, ancestor_depth, delegate) - } - pub fn attach_kind(&self) -> Attach { - *self.attach_kind.get_or_init(|| self.adapter.attach_kind()) - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Attach { - /// Create a single language server instance per subproject root. - InstancePerRoot, - /// Use one shared language server instance for all subprojects within a project. - Shared, -} - -impl Attach { - pub fn root_path( - &self, - root_subproject_path: (WorktreeId, Arc), - ) -> (WorktreeId, Arc) { - match self { - Attach::InstancePerRoot => root_subproject_path, - Attach::Shared => (root_subproject_path.0, Arc::from(Path::new(""))), - } - } } /// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application @@ -539,19 +505,6 @@ pub trait LspAdapter: 'static + Send + Sync { fn prepare_initialize_params(&self, original: InitializeParams) -> Result { Ok(original) } - fn attach_kind(&self) -> Attach { - Attach::Shared - } - fn find_project_root( - &self, - - _path: &Path, - _ancestor_depth: usize, - _: &Arc, - ) -> Option> { - // By default all language servers are rooted at the root of the worktree. - Some(Arc::from("".as_ref())) - } } async fn try_fetch_server_binary( diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 51d464d409..794ab0784e 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -96,7 +96,6 @@ struct LanguageRegistryState { available_languages: Vec, grammars: HashMap, AvailableGrammar>, lsp_adapters: HashMap>>, - all_lsp_adapters: HashMap>, available_lsp_adapters: HashMap Arc + 'static + Send + Sync>>, loading_languages: HashMap>>>>, @@ -223,7 +222,6 @@ impl LanguageRegistry { language_settings: Default::default(), loading_languages: Default::default(), lsp_adapters: Default::default(), - all_lsp_adapters: Default::default(), available_lsp_adapters: HashMap::default(), subscription: watch::channel(), theme: Default::default(), @@ -346,16 +344,12 @@ impl LanguageRegistry { adapter: Arc, ) -> Arc { let cached = CachedLspAdapter::new(adapter); - let mut state = self.state.write(); - state + self.state + .write() .lsp_adapters .entry(language_name) .or_default() .push(cached.clone()); - state - .all_lsp_adapters - .insert(cached.name.clone(), cached.clone()); - cached } @@ -395,17 +389,12 @@ impl LanguageRegistry { let adapter_name = LanguageServerName(adapter.name.into()); let capabilities = adapter.capabilities.clone(); let initializer = adapter.initializer.take(); - let adapter = CachedLspAdapter::new(Arc::new(adapter)); - { - let mut state = self.state.write(); - state - .lsp_adapters - .entry(language_name.clone()) - .or_default() - .push(adapter.clone()); - state.all_lsp_adapters.insert(adapter.name(), adapter); - } - + self.state + .write() + .lsp_adapters + .entry(language_name.clone()) + .or_default() + .push(CachedLspAdapter::new(Arc::new(adapter))); self.register_fake_language_server(adapter_name, capabilities, initializer) } @@ -418,16 +407,12 @@ impl LanguageRegistry { adapter: crate::FakeLspAdapter, ) { let language_name = language_name.into(); - let mut state = self.state.write(); - let cached_adapter = CachedLspAdapter::new(Arc::new(adapter)); - state + self.state + .write() .lsp_adapters .entry(language_name.clone()) .or_default() - .push(cached_adapter.clone()); - state - .all_lsp_adapters - .insert(cached_adapter.name(), cached_adapter); + .push(CachedLspAdapter::new(Arc::new(adapter))); } /// Register a fake language server (without the adapter) @@ -895,10 +880,6 @@ impl LanguageRegistry { .unwrap_or_default() } - pub fn adapter_for_name(&self, name: &LanguageServerName) -> Option> { - self.state.read().all_lsp_adapters.get(name).cloned() - } - pub fn update_lsp_status( &self, server_name: LanguageServerName, diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 62cfdeedcb..184a06f215 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -730,8 +730,7 @@ impl LspLogView { * Binary: {BINARY:#?} -* Registered workspace folders: -{WORKSPACE_FOLDERS} +* Running in project: {PATH:?} * Capabilities: {CAPABILITIES} @@ -739,15 +738,7 @@ impl LspLogView { NAME = server.name(), ID = server.server_id(), BINARY = server.binary(), - WORKSPACE_FOLDERS = server - .workspace_folders() - .iter() - .filter_map(|path| path - .to_file_path() - .ok() - .map(|path| path.to_string_lossy().into_owned())) - .collect::>() - .join(", "), + PATH = server.root_path(), CAPABILITIES = serde_json::to_string_pretty(&server.capabilities()) .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")), CONFIGURATION = serde_json::to_string_pretty(server.configuration()) diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index cdd094a129..3ef2747642 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -74,22 +74,6 @@ impl LspAdapter for RustLspAdapter { Self::SERVER_NAME.clone() } - fn find_project_root( - &self, - path: &Path, - ancestor_depth: usize, - delegate: &Arc, - ) -> Option> { - let mut outermost_cargo_toml = None; - for path in path.ancestors().take(ancestor_depth) { - let p = path.join("Cargo.toml").to_path_buf(); - if smol::block_on(delegate.read_text_file(p)).is_ok() { - outermost_cargo_toml = Some(Arc::from(path)); - } - } - - outermost_cargo_toml - } async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index eba4d0e870..0937b47217 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -29,7 +29,6 @@ serde.workspace = true serde_json.workspace = true schemars.workspace = true smol.workspace = true -text.workspace = true util.workspace = true release_channel.workspace = true diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 51de3bc9ce..e9fa1caac2 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -7,7 +7,6 @@ use anyhow::{anyhow, Context, Result}; use collections::HashMap; use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, Future, FutureExt}; use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, SharedString, Task}; -use notification::DidChangeWorkspaceFolders; use parking_lot::{Mutex, RwLock}; use postage::{barrier, prelude::Stream}; use schemars::{ @@ -22,14 +21,12 @@ use smol::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, process::Child, }; -use text::BufferId; use std::{ - collections::BTreeSet, ffi::{OsStr, OsString}, fmt, io::Write, - ops::{Deref, DerefMut}, + ops::DerefMut, path::PathBuf, pin::Pin, sync::{ @@ -99,9 +96,9 @@ pub struct LanguageServer { #[allow(clippy::type_complexity)] io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, + root_path: PathBuf, + working_dir: PathBuf, server: Arc>>, - workspace_folders: Arc>>, - registered_buffers: Arc>>, } /// Identifies a running language server. @@ -379,6 +376,8 @@ impl LanguageServer { Some(stderr), stderr_capture, Some(server), + root_path, + working_dir, code_action_kinds, binary, cx, @@ -404,6 +403,8 @@ impl LanguageServer { stderr: Option, stderr_capture: Arc>>, server: Option, + root_path: &Path, + working_dir: &Path, code_action_kinds: Option>, binary: LanguageServerBinary, cx: AsyncAppContext, @@ -487,9 +488,9 @@ impl LanguageServer { executor: cx.background_executor().clone(), io_tasks: Mutex::new(Some((input_task, output_task))), output_done_rx: Mutex::new(Some(output_done_rx)), + root_path: root_path.to_path_buf(), + working_dir: working_dir.to_path_buf(), server: Arc::new(Mutex::new(server)), - workspace_folders: Default::default(), - registered_buffers: Default::default(), } } @@ -614,11 +615,12 @@ impl LanguageServer { } pub fn default_initialize_params(&self, cx: &AppContext) -> InitializeParams { + let root_uri = Url::from_file_path(&self.working_dir).unwrap(); #[allow(deprecated)] InitializeParams { process_id: None, root_path: None, - root_uri: None, + root_uri: Some(root_uri.clone()), initialization_options: None, capabilities: ClientCapabilities { general: Some(GeneralClientCapabilities { @@ -785,7 +787,10 @@ impl LanguageServer { }), }, trace: None, - workspace_folders: None, + workspace_folders: Some(vec![WorkspaceFolder { + uri: root_uri, + name: Default::default(), + }]), client_info: release_channel::ReleaseChannel::try_global(cx).map(|release_channel| { ClientInfo { name: release_channel.display_name().to_string(), @@ -804,10 +809,16 @@ impl LanguageServer { /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize) pub fn initialize( mut self, - params: InitializeParams, + initialize_params: Option, configuration: Arc, cx: &AppContext, ) -> Task>> { + let params = if let Some(params) = initialize_params { + params + } else { + self.default_initialize_params(cx) + }; + cx.spawn(|_| async move { let response = self.request::(params).await?; if let Some(info) = response.server_info { @@ -1059,10 +1070,16 @@ impl LanguageServer { self.server_id } + /// Get the root path of the project the language server is running against. + pub fn root_path(&self) -> &PathBuf { + &self.root_path + } + /// Language server's binary information. pub fn binary(&self) -> &LanguageServerBinary { &self.binary } + /// Sends a RPC request to the language server. /// /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage) @@ -1190,129 +1207,6 @@ impl LanguageServer { outbound_tx.try_send(message)?; Ok(()) } - - /// Add new workspace folder to the list. - pub fn add_workspace_folder(&self, uri: Url) { - if self - .capabilities() - .workspace - .and_then(|ws| { - ws.workspace_folders.and_then(|folders| { - folders - .change_notifications - .map(|caps| matches!(caps, OneOf::Left(false))) - }) - }) - .unwrap_or(true) - { - return; - } - - let is_new_folder = self.workspace_folders.lock().insert(uri.clone()); - if is_new_folder { - let params = DidChangeWorkspaceFoldersParams { - event: WorkspaceFoldersChangeEvent { - added: vec![WorkspaceFolder { - uri, - name: String::default(), - }], - removed: vec![], - }, - }; - self.notify::(¶ms).log_err(); - } - } - /// Add new workspace folder to the list. - pub fn remove_workspace_folder(&self, uri: Url) { - if self - .capabilities() - .workspace - .and_then(|ws| { - ws.workspace_folders.and_then(|folders| { - folders - .change_notifications - .map(|caps| !matches!(caps, OneOf::Left(false))) - }) - }) - .unwrap_or(true) - { - return; - } - let was_removed = self.workspace_folders.lock().remove(&uri); - if was_removed { - let params = DidChangeWorkspaceFoldersParams { - event: WorkspaceFoldersChangeEvent { - added: vec![], - removed: vec![WorkspaceFolder { - uri, - name: String::default(), - }], - }, - }; - self.notify::(¶ms).log_err(); - } - } - pub fn set_workspace_folders(&self, folders: BTreeSet) { - let mut workspace_folders = self.workspace_folders.lock(); - let added: Vec<_> = folders - .iter() - .map(|uri| WorkspaceFolder { - uri: uri.clone(), - name: String::default(), - }) - .collect(); - - let removed: Vec<_> = std::mem::replace(&mut *workspace_folders, folders) - .into_iter() - .map(|uri| WorkspaceFolder { - uri: uri.clone(), - name: String::default(), - }) - .collect(); - let should_notify = !added.is_empty() || !removed.is_empty(); - - if should_notify { - let params = DidChangeWorkspaceFoldersParams { - event: WorkspaceFoldersChangeEvent { added, removed }, - }; - self.notify::(¶ms).log_err(); - } - } - - pub fn workspace_folders(&self) -> impl Deref> + '_ { - self.workspace_folders.lock() - } - - pub fn register_buffer( - &self, - buffer_id: BufferId, - uri: Url, - language_id: String, - version: i32, - initial_text: String, - ) { - let previous_value = self - .registered_buffers - .lock() - .insert(buffer_id, uri.clone()); - if previous_value.is_none() { - self.notify::(&DidOpenTextDocumentParams { - text_document: TextDocumentItem::new(uri, language_id, version, initial_text), - }) - .log_err(); - } else { - debug_assert_eq!(previous_value, Some(uri)); - } - } - - pub fn unregister_buffer(&self, buffer_id: BufferId) { - if let Some(path) = self.registered_buffers.lock().remove(&buffer_id) { - self.notify::(&DidCloseTextDocumentParams { - text_document: TextDocumentIdentifier::new(path), - }) - .log_err(); - } - } } impl Drop for LanguageServer { @@ -1394,6 +1288,8 @@ impl FakeLanguageServer { let (stdout_writer, stdout_reader) = async_pipe::pipe(); let (notifications_tx, notifications_rx) = channel::unbounded(); + let root = Self::root_path(); + let server_name = LanguageServerName(name.clone().into()); let process_name = Arc::from(name.as_str()); let mut server = LanguageServer::new_internal( @@ -1404,6 +1300,8 @@ impl FakeLanguageServer { None::, Arc::new(Mutex::new(None)), None, + root, + root, None, binary.clone(), cx.clone(), @@ -1421,6 +1319,8 @@ impl FakeLanguageServer { None::, Arc::new(Mutex::new(None)), None, + root, + root, None, binary, cx.clone(), @@ -1457,6 +1357,16 @@ impl FakeLanguageServer { (server, fake) } + + #[cfg(target_os = "windows")] + fn root_path() -> &'static Path { + Path::new("C:\\") + } + + #[cfg(not(target_os = "windows"))] + fn root_path() -> &'static Path { + Path::new("/") + } } #[cfg(any(test, feature = "test-support"))] @@ -1644,14 +1554,12 @@ mod tests { }) .detach(); + let initialize_params = None; + let configuration = DidChangeConfigurationParams { + settings: Default::default(), + }; let server = cx - .update(|cx| { - let params = server.default_initialize_params(cx); - let configuration = DidChangeConfigurationParams { - settings: Default::default(), - }; - server.initialize(params, configuration.into(), cx) - }) + .update(|cx| server.initialize(initialize_params, configuration.into(), cx)) .await .unwrap(); server diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index c0d770c5d5..b9fcd8df0e 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -283,13 +283,13 @@ impl Prettier { ) .context("prettier server creation")?; + let initialize_params = None; + let configuration = lsp::DidChangeConfigurationParams { + settings: Default::default(), + }; let server = cx .update(|cx| { - let params = server.default_initialize_params(cx); - let configuration = lsp::DidChangeConfigurationParams { - settings: Default::default(), - }; - executor.spawn(server.initialize(params, configuration.into(), cx)) + executor.spawn(server.initialize(initialize_params, configuration.into(), cx)) })? .await .context("prettier server initialization")?; diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 7c2c546e70..5149a818cf 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -43,7 +43,6 @@ log.workspace = true lsp.workspace = true node_runtime.workspace = true image.workspace = true -once_cell.workspace = true parking_lot.workspace = true pathdiff.workspace = true paths.workspace = true diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index e69b7b95b0..c58fcb1351 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -942,11 +942,9 @@ fn language_server_for_buffer( ) -> Result<(Arc, Arc)> { lsp_store .update(cx, |lsp_store, cx| { - buffer.update(cx, |buffer, cx| { - lsp_store - .language_server_for_local_buffer(buffer, server_id, cx) - .map(|(adapter, server)| (adapter.clone(), server.clone())) - }) + lsp_store + .language_server_for_local_buffer(buffer.read(cx), server_id, cx) + .map(|(adapter, server)| (adapter.clone(), server.clone())) })? .ok_or_else(|| anyhow!("no language server found for buffer")) } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 9e3960c925..ba88b5e485 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -6,7 +6,6 @@ use crate::{ lsp_ext_command, prettier_store::{self, PrettierStore, PrettierStoreEvent}, project_settings::{LspSettings, ProjectSettings}, - project_tree::{LanguageServerTree, LaunchDisposition, ProjectTree}, relativize_path, resolve_path, toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent}, @@ -39,9 +38,9 @@ use language::{ proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, - LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, LocalFile, LspAdapter, - LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, - Unclipped, + LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, LocalFile, + LspAdapter, LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, + Transaction, Unclipped, }; use lsp::{ notification::DidRenameFiles, CodeActionKind, CompletionContext, DiagnosticSeverity, @@ -49,8 +48,8 @@ use lsp::{ FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher, InsertTextFormat, LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, LanguageServerName, LspRequestFuture, MessageActionItem, MessageType, OneOf, - RenameFilesParams, ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, WillRenameFiles, - WorkDoneProgressCancelParams, WorkspaceFolder, + RenameFilesParams, ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, Url, + WillRenameFiles, WorkDoneProgressCancelParams, WorkspaceFolder, }; use node_runtime::read_package_installed_version; use parking_lot::Mutex; @@ -79,7 +78,6 @@ use std::{ time::{Duration, Instant}, }; use text::{Anchor, BufferId, LineEnding, OffsetRangeExt}; -use url::Url; use util::{ debug_panic, defer, maybe, merge_json_value_into, paths::SanitizedPath, post_inc, ResultExt, TryFutureExt as _, @@ -132,14 +130,13 @@ impl FormatTrigger { } pub struct LocalLspStore { - weak: WeakModel, worktree_store: Model, toolchain_store: Model, http_client: Arc, environment: Model, fs: Arc, languages: Arc, - language_server_ids: HashMap<(WorktreeId, LanguageServerName), BTreeSet>, + language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>, yarn: Model, pub language_servers: HashMap, buffers_being_formatted: HashSet, @@ -152,6 +149,7 @@ pub struct LocalLspStore { supplementary_language_servers: HashMap)>, prettier_store: Model, + current_lsp_settings: HashMap, next_diagnostic_group_id: usize, diagnostics: HashMap< WorktreeId, @@ -165,7 +163,7 @@ pub struct LocalLspStore { >, buffer_snapshots: HashMap>>, // buffer_id -> server_id -> vec of snapshots _subscription: gpui::Subscription, - lsp_tree: Model, + registered_buffers: HashMap, } impl LocalLspStore { @@ -174,15 +172,26 @@ impl LocalLspStore { worktree_handle: &Model, delegate: Arc, adapter: Arc, - settings: Arc, - cx: &mut AppContext, - ) -> LanguageServerId { + cx: &mut ModelContext, + ) { let worktree = worktree_handle.read(cx); let worktree_id = worktree.id(); let root_path = worktree.abs_path(); let key = (worktree_id, adapter.name.clone()); - let override_options = settings.initialization_options.clone(); + if self.language_server_ids.contains_key(&key) { + return; + } + + let project_settings = ProjectSettings::get( + Some(SettingsLocation { + worktree_id, + path: Path::new(""), + }), + cx, + ); + let lsp = project_settings.lsp.get(&adapter.name); + let override_options = lsp.and_then(|s| s.initialization_options.clone()); let stderr_capture = Arc::new(Mutex::new(Some(String::new()))); @@ -198,13 +207,12 @@ impl LocalLspStore { let adapter = adapter.clone(); let server_name = adapter.name.clone(); let stderr_capture = stderr_capture.clone(); - #[cfg(any(test, feature = "test-support"))] - let lsp_store = self.weak.clone(); - move |cx| async move { + move |_lsp_store, cx| async move { let binary = binary.await?; + #[cfg(any(test, feature = "test-support"))] - if let Some(server) = lsp_store + if let Some(server) = _lsp_store .update(&mut cx.clone(), |this, cx| { this.languages.create_fake_language_server( server_id, @@ -231,15 +239,13 @@ impl LocalLspStore { } }); - let pending_workspace_folders: Arc>> = Default::default(); - let startup = { + let state = LanguageServerState::Starting({ let server_name = adapter.name.0.clone(); let delegate = delegate as Arc; let key = key.clone(); let adapter = adapter.clone(); - let this = self.weak.clone(); - let pending_workspace_folders = pending_workspace_folders.clone(); - cx.spawn(move |mut cx| async move { + + cx.spawn(move |this, mut cx| async move { let result = { let delegate = delegate.clone(); let adapter = adapter.clone(); @@ -286,7 +292,7 @@ impl LocalLspStore { let language_server = cx .update(|cx| { language_server.initialize( - initialization_params, + Some(initialization_params), did_change_configuration_params.clone(), cx, ) @@ -320,7 +326,6 @@ impl LocalLspStore { server.clone(), server_id, key, - pending_workspace_folders, &mut cx, ); }) @@ -343,18 +348,82 @@ impl LocalLspStore { } } }) - }; - let state = LanguageServerState::Starting { - startup, - pending_workspace_folders, - }; + }); self.language_servers.insert(server_id, state); - self.language_server_ids - .entry(key) - .or_default() - .insert(server_id); - server_id + self.language_server_ids.insert(key, server_id); + } + + pub fn start_language_servers( + &mut self, + worktree: &Model, + language: LanguageName, + cx: &mut ModelContext, + ) { + let root_file = worktree + .update(cx, |tree, cx| tree.root_file(cx)) + .map(|f| f as _); + let settings = language_settings(Some(language.clone()), root_file.as_ref(), cx); + if !settings.enable_language_server { + return; + } + + let available_lsp_adapters = self.languages.clone().lsp_adapters(&language); + let available_language_servers = available_lsp_adapters + .iter() + .map(|lsp_adapter| lsp_adapter.name.clone()) + .collect::>(); + + let desired_language_servers = + settings.customized_language_servers(&available_language_servers); + + let mut enabled_lsp_adapters: Vec> = Vec::new(); + for desired_language_server in desired_language_servers { + if let Some(adapter) = available_lsp_adapters + .iter() + .find(|adapter| adapter.name == desired_language_server) + { + enabled_lsp_adapters.push(adapter.clone()); + continue; + } + + if let Some(adapter) = self + .languages + .load_available_lsp_adapter(&desired_language_server) + { + self.languages + .register_lsp_adapter(language.clone(), adapter.adapter.clone()); + enabled_lsp_adapters.push(adapter); + continue; + } + + log::warn!( + "no language server found matching '{}'", + desired_language_server.0 + ); + } + + for adapter in &enabled_lsp_adapters { + let delegate = LocalLspAdapterDelegate::new( + self.languages.clone(), + &self.environment, + cx.weak_model(), + &worktree, + self.http_client.clone(), + self.fs.clone(), + cx, + ); + self.start_language_server(worktree, delegate, adapter.clone(), cx); + } + + // After starting all the language servers, reorder them to reflect the desired order + // based on the settings. + // + // This is done, in part, to ensure that language servers loaded at different points + // (e.g., native vs extension) still end up in the right order at the end, rather than + // it being based on which language server happened to be loaded in first. + self.languages + .reorder_language_servers(&language, enabled_lsp_adapters); } fn get_language_server_binary( @@ -362,7 +431,7 @@ impl LocalLspStore { adapter: Arc, delegate: Arc, allow_binary_download: bool, - cx: &mut AppContext, + cx: &mut ModelContext, ) -> Task> { let settings = ProjectSettings::get( Some(SettingsLocation { @@ -377,7 +446,7 @@ impl LocalLspStore { if settings.as_ref().is_some_and(|b| b.path.is_some()) { let settings = settings.unwrap(); - return cx.spawn(|_| async move { + return cx.spawn(|_, _| async move { Ok(LanguageServerBinary { path: PathBuf::from(&settings.path.unwrap()), env: Some(delegate.shell_env().await), @@ -398,7 +467,7 @@ impl LocalLspStore { allow_binary_download, }; let toolchains = self.toolchain_store.read(cx).as_language_toolchain_store(); - cx.spawn(|mut cx| async move { + cx.spawn(|_, mut cx| async move { let binary_result = adapter .clone() .get_language_server_command( @@ -498,16 +567,14 @@ impl LocalLspStore { else { return Ok(None); }; - let root = server.workspace_folders(); - Ok(Some( - root.iter() - .cloned() - .map(|uri| WorkspaceFolder { - uri, - name: Default::default(), - }) - .collect(), - )) + let root = server.root_path(); + let Ok(uri) = Url::from_file_path(&root) else { + return Ok(None); + }; + Ok(Some(vec![WorkspaceFolder { + uri, + name: Default::default(), + }])) } } }) @@ -929,7 +996,7 @@ impl LocalLspStore { use LanguageServerState::*; match server_state { Running { server, .. } => server.shutdown()?.await, - Starting { startup, .. } => startup.await?.shutdown()?.await, + Starting(task) => task.await?.shutdown()?.await, } }) .collect::>(); @@ -945,58 +1012,42 @@ impl LocalLspStore { ) -> impl Iterator> { self.language_server_ids .iter() - .flat_map(move |((language_server_path, _), ids)| { - ids.iter().filter_map(move |id| { - if *language_server_path != worktree_id { - return None; - } + .filter_map(move |((language_server_worktree_id, _), id)| { + if *language_server_worktree_id == worktree_id { if let Some(LanguageServerState::Running { server, .. }) = self.language_servers.get(id) { return Some(server); - } else { - None } - }) + } + None }) } - fn language_server_ids_for_buffer( + pub(crate) fn language_server_ids_for_buffer( &self, buffer: &Buffer, - cx: &mut AppContext, + cx: &AppContext, ) -> Vec { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { let worktree_id = file.worktree_id(cx); - - let Some(path): Option> = file.path().parent().map(Arc::from) else { - return vec![]; - }; - let worktree_path = ProjectPath { worktree_id, path }; - let Some(worktree) = self - .worktree_store - .read(cx) - .worktree_for_id(worktree_id, cx) - else { - return vec![]; - }; - let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx); - let root = self.lsp_tree.update(cx, |this, cx| { - this.get(worktree_path, &language.name(), delegate, cx) - .filter_map(|node| node.server_id()) - .collect::>() - }); - - root + self.languages + .lsp_adapters(&language.name()) + .iter() + .flat_map(|adapter| { + let key = (worktree_id, adapter.name.clone()); + self.language_server_ids.get(&key).copied() + }) + .collect() } else { Vec::new() } } - fn language_servers_for_buffer<'a>( + pub(crate) fn language_servers_for_buffer<'a>( &'a self, buffer: &'a Buffer, - cx: &'a mut AppContext, + cx: &'a AppContext, ) -> impl Iterator, &'a Arc)> { self.language_server_ids_for_buffer(buffer, cx) .into_iter() @@ -1011,7 +1062,7 @@ impl LocalLspStore { fn primary_language_server_for_buffer<'a>( &'a self, buffer: &'a Buffer, - cx: &'a mut AppContext, + cx: &'a AppContext, ) -> Option<(&'a Arc, &'a Arc)> { // The list of language servers is ordered based on the `language_servers` setting // for each language, thus we can consider the first one in the list to be the @@ -1057,22 +1108,20 @@ impl LocalLspStore { for buffer in &buffers { let (primary_adapter_and_server, adapters_and_servers) = lsp_store.update(&mut cx, |lsp_store, cx| { - let adapters_and_servers = buffer.handle.update(cx, |buffer, cx| { - lsp_store - .as_local() - .unwrap() - .language_servers_for_buffer(buffer, cx) - .map(|(adapter, lsp)| (adapter.clone(), lsp.clone())) - .collect::>() - }); + let buffer = buffer.handle.read(cx); - let primary_adapter = buffer.handle.update(cx, |buffer, cx| { - lsp_store - .as_local() - .unwrap() - .primary_language_server_for_buffer(buffer, cx) - .map(|(adapter, lsp)| (adapter.clone(), lsp.clone())) - }); + let adapters_and_servers = lsp_store + .as_local() + .unwrap() + .language_servers_for_buffer(buffer, cx) + .map(|(adapter, lsp)| (adapter.clone(), lsp.clone())) + .collect::>(); + + let primary_adapter = lsp_store + .as_local() + .unwrap() + .primary_language_server_for_buffer(buffer, cx) + .map(|(adapter, lsp)| (adapter.clone(), lsp.clone())); (primary_adapter, adapters_and_servers) })?; @@ -1681,8 +1730,7 @@ impl LocalLspStore { ) { let buffer = buffer_handle.read(cx); - let file = buffer.file().cloned(); - let Some(file) = File::from_dyn(file.as_ref()) else { + let Some(file) = File::from_dyn(buffer.file()) else { return; }; if !file.is_local() { @@ -1690,6 +1738,7 @@ impl LocalLspStore { } let worktree_id = file.worktree_id(cx); + let language = buffer.language().cloned(); if let Some(diagnostics) = self.diagnostics.get(&worktree_id) { for (server_id, diagnostics) in @@ -1699,6 +1748,45 @@ impl LocalLspStore { .log_err(); } } + + let Some(language) = language else { + return; + }; + for adapter in self.languages.lsp_adapters(&language.name()) { + let server = self + .language_server_ids + .get(&(worktree_id, adapter.name.clone())) + .and_then(|id| self.language_servers.get(id)) + .and_then(|server_state| { + if let LanguageServerState::Running { server, .. } = server_state { + Some(server.clone()) + } else { + None + } + }); + let server = match server { + Some(server) => server, + None => continue, + }; + + buffer_handle.update(cx, |buffer, cx| { + buffer.set_completion_triggers( + server.server_id(), + server + .capabilities() + .completion_provider + .as_ref() + .and_then(|provider| { + provider + .trigger_characters + .as_ref() + .map(|characters| characters.iter().cloned().collect()) + }) + .unwrap_or_default(), + cx, + ); + }); + } } pub(crate) fn reset_buffer( @@ -1710,35 +1798,14 @@ impl LocalLspStore { buffer.update(cx, |buffer, cx| { let worktree_id = old_file.worktree_id(cx); + let ids = &self.language_server_ids; + if let Some(language) = buffer.language().cloned() { - let Some(worktree) = self - .worktree_store - .read(cx) - .worktree_for_id(worktree_id, cx) - else { - return; - }; - let Some(path): Option> = old_file.path().parent().map(Arc::from) else { - return; - }; - - let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx); - let nodes = self.lsp_tree.update(cx, |this, cx| { - this.get( - ProjectPath { worktree_id, path }, - &language.name(), - delegate, - cx, - ) - .collect::>() - }); - for node in nodes { - let Some(server_id) = node.server_id() else { - continue; - }; - - buffer.update_diagnostics(server_id, DiagnosticSet::new([], buffer), cx); - buffer.set_completion_triggers(server_id, Default::default(), cx); + for adapter in self.languages.lsp_adapters(&language.name()) { + if let Some(server_id) = ids.get(&(worktree_id, adapter.name.clone())) { + buffer.update_diagnostics(*server_id, DiagnosticSet::new([], buffer), cx); + buffer.set_completion_triggers(*server_id, Default::default(), cx); + } } } }); @@ -1842,186 +1909,81 @@ impl LocalLspStore { }; let initial_snapshot = buffer.text_snapshot(); let worktree_id = file.worktree_id(cx); + let worktree = file.worktree.clone(); let Some(language) = buffer.language().cloned() else { return; }; - let Some(path): Option> = file.path().parent().map(Arc::from) else { - return; - }; - let Some(worktree) = self - .worktree_store - .read(cx) - .worktree_for_id(worktree_id, cx) - else { - return; - }; - let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx); - let servers = self.lsp_tree.clone().update(cx, |this, cx| { - this.get( - ProjectPath { worktree_id, path }, - &language.name(), - delegate.clone(), - cx, - ) - .collect::>() - }); - let servers = servers - .into_iter() - .filter_map(|server_node| { - let server_id = server_node.server_id_or_init( - |LaunchDisposition { - server_name, - attach, - path, - settings, - }| match attach { - language::Attach::InstancePerRoot => { - // todo: handle instance per root proper. - if let Some(server_ids) = self - .language_server_ids - .get(&(worktree_id, server_name.clone())) - { - server_ids.iter().cloned().next().unwrap() - } else { - let language_name = language.name(); - - self.start_language_server( - &worktree, - delegate.clone(), - self.languages - .lsp_adapters(&language_name) - .into_iter() - .find(|adapter| &adapter.name() == server_name) - .expect("To find LSP adapter"), - settings, - cx, - ) - } - } - language::Attach::Shared => { - let uri = Url::from_directory_path( - worktree.read(cx).abs_path().join(&path.path), - ); - let key = (worktree_id, server_name.clone()); - if !self.language_server_ids.contains_key(&key) { - let language_name = language.name(); - self.start_language_server( - &worktree, - delegate.clone(), - self.languages - .lsp_adapters(&language_name) - .into_iter() - .find(|adapter| &adapter.name() == server_name) - .expect("To find LSP adapter"), - settings, - cx, - ); - } - if let Some(server_ids) = self - .language_server_ids - .get(&key) - { - debug_assert_eq!(server_ids.len(), 1); - let server_id = server_ids.iter().cloned().next().unwrap(); - - if let Some(state) = self.language_servers.get(&server_id) { - if let Ok(uri) = uri { - state.add_workspace_folder(uri); - }; - } - server_id - } else { - unreachable!("Language server ID should be available, as it's registered on demand") - } - } - }, - )?; - let server_state = self.language_servers.get(&server_id)?; - if let LanguageServerState::Running { server, .. } = server_state { - Some(server.clone()) - } else { - None - } - }) - .collect::>(); - for server in servers { - buffer_handle.update(cx, |buffer, cx| { - buffer.set_completion_triggers( - server.server_id(), - server - .capabilities() - .completion_provider - .as_ref() - .and_then(|provider| { - provider - .trigger_characters - .as_ref() - .map(|characters| characters.iter().cloned().collect()) - }) - .unwrap_or_default(), - cx, - ); - }); - } + self.start_language_servers(&worktree, language.name(), cx); for adapter in self.languages.lsp_adapters(&language.name()) { - let servers = self + let server = self .language_server_ids .get(&(worktree_id, adapter.name.clone())) - .map(|ids| { - ids.iter().flat_map(|id| { - self.language_servers.get(id).and_then(|server_state| { - if let LanguageServerState::Running { server, .. } = server_state { - Some(server.clone()) - } else { - None - } - }) - }) + .and_then(|id| self.language_servers.get(id)) + .and_then(|server_state| { + if let LanguageServerState::Running { server, .. } = server_state { + Some(server.clone()) + } else { + None + } }); - let servers = match servers { + let server = match server { Some(server) => server, None => continue, }; - for server in servers { - server.register_buffer( - buffer_id, - uri.clone(), - adapter.language_id(&language.name()), - 0, - initial_snapshot.text(), - ); + server + .notify::(&lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( + uri.clone(), + adapter.language_id(&language.name()), + 0, + initial_snapshot.text(), + ), + }) + .log_err(); - let snapshot = LspBufferSnapshot { - version: 0, - snapshot: initial_snapshot.clone(), - }; - self.buffer_snapshots - .entry(buffer_id) - .or_default() - .insert(server.server_id(), vec![snapshot]); - } + let snapshot = LspBufferSnapshot { + version: 0, + snapshot: initial_snapshot.clone(), + }; + self.buffer_snapshots + .entry(buffer_id) + .or_default() + .insert(server.server_id(), vec![snapshot]); } } - pub(crate) fn unregister_old_buffer_from_language_servers( &mut self, buffer: &Model, + old_file: &File, + cx: &mut AppContext, ) { - self.unregister_buffer_from_language_servers(buffer, cx); + let old_path = match old_file.as_local() { + Some(local) => local.abs_path(cx), + None => return, + }; + let file_url = lsp::Url::from_file_path(old_path).unwrap(); + self.unregister_buffer_from_language_servers(buffer, file_url, cx); } pub(crate) fn unregister_buffer_from_language_servers( &mut self, buffer: &Model, + file_url: lsp::Url, cx: &mut AppContext, ) { buffer.update(cx, |buffer, cx| { self.buffer_snapshots.remove(&buffer.remote_id()); for (_, language_server) in self.language_servers_for_buffer(buffer, cx) { - language_server.unregister_buffer(buffer.remote_id()); + language_server + .notify::( + &lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new(file_url.clone()), + }, + ) + .log_err(); } }); } @@ -2547,46 +2509,6 @@ impl LocalLspStore { failure_reason: None, }) } - - fn remove_worktree( - &mut self, - id_to_remove: WorktreeId, - cx: &mut ModelContext<'_, LspStore>, - ) -> Vec { - self.diagnostics.remove(&id_to_remove); - self.prettier_store.update(cx, |prettier_store, cx| { - prettier_store.remove_worktree(id_to_remove, cx); - }); - - let mut servers_to_remove = BTreeMap::default(); - let mut servers_to_preserve = HashSet::default(); - for ((path, server_name), ref server_ids) in &self.language_server_ids { - if *path == id_to_remove { - servers_to_remove.extend(server_ids.iter().map(|id| (*id, server_name.clone()))); - } else { - servers_to_preserve.extend(server_ids.iter().cloned()); - } - } - servers_to_remove.retain(|server_id, _| !servers_to_preserve.contains(server_id)); - - for (server_id_to_remove, _) in &servers_to_remove { - self.language_server_ids - .values_mut() - .for_each(|server_ids| { - server_ids.remove(server_id_to_remove); - }); - self.language_server_watched_paths - .remove(&server_id_to_remove); - self.language_server_paths_watched_for_rename - .remove(&server_id_to_remove); - self.last_workspace_edits_by_language_server - .remove(&server_id_to_remove); - self.language_servers.remove(&server_id_to_remove); - cx.emit(LspStoreEvent::LanguageServerRemoved(*server_id_to_remove)); - } - servers_to_remove.into_keys().collect() - } - fn rebuild_watched_paths_inner<'a>( &'a self, language_server_id: LanguageServerId, @@ -2923,7 +2845,6 @@ pub struct LanguageServerStatus { struct CoreSymbol { pub language_server_name: LanguageServerName, pub source_worktree_id: WorktreeId, - pub source_language_server_id: LanguageServerId, pub path: ProjectPath, pub name: String, pub kind: lsp::SymbolKind, @@ -3003,6 +2924,23 @@ impl LspStore { } } + pub fn swap_current_lsp_settings( + &mut self, + new_settings: HashMap, + ) -> Option> { + match &mut self.mode { + LspStoreMode::Local(LocalLspStore { + current_lsp_settings, + .. + }) => { + let ret = mem::take(current_lsp_settings); + *current_lsp_settings = new_settings; + Some(ret) + } + LspStoreMode::Remote(_) => None, + } + } + #[allow(clippy::too_many_arguments)] pub fn new_local( buffer_store: Model, @@ -3031,10 +2969,8 @@ impl LspStore { let (sender, receiver) = watch::channel(); (Self::maintain_workspace_config(receiver, cx), sender) }; - let project_tree = ProjectTree::new(worktree_store.clone(), cx); Self { mode: LspStoreMode::Local(LocalLspStore { - weak: cx.weak_model(), worktree_store: worktree_store.clone(), toolchain_store: toolchain_store.clone(), supplementary_language_servers: Default::default(), @@ -3045,6 +2981,7 @@ impl LspStore { language_server_watched_paths: Default::default(), language_server_paths_watched_for_rename: Default::default(), language_server_watcher_registrations: Default::default(), + current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(), buffers_being_formatted: Default::default(), buffer_snapshots: Default::default(), prettier_store, @@ -3057,7 +2994,7 @@ impl LspStore { _subscription: cx.on_app_quit(|this, cx| { this.as_local_mut().unwrap().shutdown_language_servers(cx) }), - lsp_tree: LanguageServerTree::new(project_tree, languages.clone(), cx), + registered_buffers: HashMap::default(), }), last_formatting_failure: None, downstream_client: None, @@ -3071,7 +3008,7 @@ impl LspStore { active_entry: None, _maintain_workspace_config, - _maintain_buffer_languages: Self::maintain_buffer_languages(languages, cx), + _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx), } } @@ -3130,6 +3067,17 @@ impl LspStore { } } + fn worktree_for_id( + &self, + worktree_id: WorktreeId, + cx: &ModelContext, + ) -> Result> { + self.worktree_store + .read(cx) + .worktree_for_id(worktree_id, cx) + .ok_or_else(|| anyhow!("worktree not found")) + } + fn on_buffer_store_event( &mut self, _: Model, @@ -3141,19 +3089,22 @@ impl LspStore { self.on_buffer_added(buffer, cx).log_err(); } BufferStoreEvent::BufferChangedFilePath { buffer, old_file } => { - if let Some(local) = self.as_local_mut() { - if let Some(old_file) = File::from_dyn(old_file.as_ref()) { + let buffer_id = buffer.read(cx).remote_id(); + if let Some(old_file) = File::from_dyn(old_file.as_ref()) { + if let Some(local) = self.as_local_mut() { local.reset_buffer(buffer, old_file, cx); - - local.unregister_old_buffer_from_language_servers(buffer, cx); + if local.registered_buffers.contains_key(&buffer_id) { + local.unregister_old_buffer_from_language_servers(buffer, old_file, cx); + } } } self.detect_language_for_buffer(buffer, cx); if let Some(local) = self.as_local_mut() { local.initialize_buffer(buffer, cx); - - local.register_buffer_with_language_servers(buffer, cx); + if local.registered_buffers.contains_key(&buffer_id) { + local.register_buffer_with_language_servers(buffer, cx); + } } } BufferStoreEvent::BufferDropped(_) => {} @@ -3284,6 +3235,8 @@ impl LspStore { buffer: &Model, cx: &mut ModelContext, ) -> OpenLspBufferHandle { + let buffer_id = buffer.read(cx).remote_id(); + let handle = cx.new_model(|_| buffer.clone()); if let Some(local) = self.as_local_mut() { @@ -3293,12 +3246,25 @@ impl LspStore { if !file.is_local() { return handle; } - - local.register_buffer_with_language_servers(buffer, cx); + let refcount = local.registered_buffers.entry(buffer_id).or_insert(0); + *refcount += 1; + if *refcount == 1 { + local.register_buffer_with_language_servers(buffer, cx); + } cx.observe_release(&handle, move |this, buffer, cx| { let local = this.as_local_mut().unwrap(); - local.unregister_old_buffer_from_language_servers(&buffer, cx); + let Some(refcount) = local.registered_buffers.get_mut(&buffer_id) else { + debug_panic!("bad refcounting"); + return; + }; + *refcount -= 1; + if *refcount == 0 { + local.registered_buffers.remove(&buffer_id); + if let Some(file) = File::from_dyn(buffer.read(cx).file()).cloned() { + local.unregister_old_buffer_from_language_servers(&buffer, &file, cx); + } + } }) .detach(); } else if let Some((upstream_client, upstream_project_id)) = self.upstream_client() { @@ -3342,10 +3308,14 @@ impl LspStore { .update(cx, |buffer, cx| buffer.set_language(None, cx)); if let Some(local) = this.as_local_mut() { local.reset_buffer(&buffer, &f, cx); - - local.unregister_old_buffer_from_language_servers( - &buffer, cx, - ); + if local + .registered_buffers + .contains_key(&buffer.read(cx).remote_id()) + { + local.unregister_old_buffer_from_language_servers( + &buffer, &f, cx, + ); + } } } } @@ -3371,7 +3341,12 @@ impl LspStore { this.detect_language_for_buffer(&buffer, cx); if let Some(local) = this.as_local_mut() { local.initialize_buffer(&buffer, cx); - local.register_buffer_with_language_servers(&buffer, cx); + if local + .registered_buffers + .contains_key(&buffer.read(cx).remote_id()) + { + local.register_buffer_with_language_servers(&buffer, cx); + } } } @@ -3421,8 +3396,17 @@ impl LspStore { cx: &mut ModelContext, ) { let buffer_file = buffer.read(cx).file().cloned(); + let buffer_id = buffer.read(cx).remote_id(); if let Some(local_store) = self.as_local_mut() { - local_store.unregister_buffer_from_language_servers(buffer, cx); + if local_store.registered_buffers.contains_key(&buffer_id) { + if let Some(abs_path) = + File::from_dyn(buffer_file.as_ref()).map(|file| file.abs_path(cx)) + { + if let Some(file_url) = lsp::Url::from_file_path(&abs_path).log_err() { + local_store.unregister_buffer_from_language_servers(buffer, file_url, cx); + } + } + } } buffer.update(cx, |buffer, cx| { if buffer.language().map_or(true, |old_language| { @@ -3440,7 +3424,9 @@ impl LspStore { let worktree = file.worktree.clone(); if let Some(local) = self.as_local_mut() { - local.register_buffer_with_language_servers(buffer, cx); + if local.registered_buffers.contains_key(&buffer_id) { + local.register_buffer_with_language_servers(buffer, cx); + } } Some(worktree.read(cx).id()) } else { @@ -3515,23 +3501,23 @@ impl LspStore { cx, ); } - - let Some(language_server) = buffer_handle.update(cx, |buffer, cx| match server { - LanguageServerToQuery::Primary => self - .as_local() - .and_then(|local| local.primary_language_server_for_buffer(buffer, cx)) - .map(|(_, server)| server.clone()), + let buffer = buffer_handle.read(cx); + let language_server = match server { + LanguageServerToQuery::Primary => { + match self + .as_local() + .and_then(|local| local.primary_language_server_for_buffer(buffer, cx)) + { + Some((_, server)) => Some(Arc::clone(server)), + None => return Task::ready(Ok(Default::default())), + } + } LanguageServerToQuery::Other(id) => self .language_server_for_local_buffer(buffer, id, cx) .map(|(_, server)| Arc::clone(server)), - }) else { - return Task::ready(Ok(Default::default())); }; - - let buffer = buffer_handle.read(cx); let file = File::from_dyn(buffer.file()).and_then(File::as_local); - - if let Some(file) = file { + if let (Some(file), Some(language_server)) = (file, language_server) { let lsp_params = match request.to_lsp_params_or_response( &file.abs_path(cx), buffer, @@ -3540,7 +3526,6 @@ impl LspStore { ) { Ok(LspParamsOrResponse::Params(lsp_params)) => lsp_params, Ok(LspParamsOrResponse::Response(response)) => return Task::ready(Ok(response)), - Err(err) => { let message = format!( "{} via {} failed: {}", @@ -3552,7 +3537,6 @@ impl LspStore { return Task::ready(Err(anyhow!(message))); } }; - let status = request.status(); if !request.check_capabilities(language_server.adapter_server_capabilities()) { return Task::ready(Ok(Default::default())); @@ -3627,13 +3611,25 @@ impl LspStore { } fn on_settings_changed(&mut self, cx: &mut ModelContext) { + let mut language_servers_to_start = Vec::new(); let mut language_formatters_to_check = Vec::new(); for buffer in self.buffer_store.read(cx).buffers() { let buffer = buffer.read(cx); let buffer_file = File::from_dyn(buffer.file()); let buffer_language = buffer.language(); let settings = language_settings(buffer_language.map(|l| l.name()), buffer.file(), cx); - if buffer_language.is_some() { + if let Some(language) = buffer_language { + if settings.enable_language_server + && self + .as_local() + .unwrap() + .registered_buffers + .contains_key(&buffer.remote_id()) + { + if let Some(file) = buffer_file { + language_servers_to_start.push((file.worktree.clone(), language.name())); + } + } language_formatters_to_check.push(( buffer_file.map(|f| f.worktree_id(cx)), settings.into_owned(), @@ -3641,66 +3637,80 @@ impl LspStore { } } - let mut to_stop = Vec::new(); - if let Some(local) = self.as_local_mut() { - local.lsp_tree.clone().update(cx, |this, cx| { - let mut get_adapter = { - let languages = local.languages.clone(); - let environment = local.environment.clone(); - let weak = local.weak.clone(); - let worktree_store = local.worktree_store.clone(); - let http_client = local.http_client.clone(); - let fs = local.fs.clone(); - move |worktree_id, cx: &mut AppContext| -> Option> { - let worktree = worktree_store.read(cx).worktree_for_id(worktree_id, cx)?; - Some(LocalLspAdapterDelegate::new( - languages.clone(), - &environment, - weak.clone(), - &worktree, - http_client.clone(), - fs.clone(), - cx, - )) - } - }; + let mut language_servers_to_stop = Vec::new(); + let mut language_servers_to_restart = Vec::new(); + let languages = self.languages.to_vec(); - this.on_settings_changed( - &mut get_adapter, - &mut |disposition, cx| { - let worktree = local - .worktree_store - .read(cx) - .worktree_for_id(disposition.path.worktree_id, cx) - .expect("Worktree ID to be valid"); - let delegate = - LocalLspAdapterDelegate::from_local_lsp(local, &worktree, cx); - let adapter = local - .languages - .adapter_for_name(disposition.server_name) - .expect("Adapter to be available"); - local.start_language_server( - &worktree, - delegate, - adapter, - disposition.settings, - cx, - ) - }, - &mut |id| to_stop.push(id), - cx, - ); + let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone(); + let Some(current_lsp_settings) = self.swap_current_lsp_settings(new_lsp_settings.clone()) + else { + return; + }; + for (worktree_id, started_lsp_name) in + self.as_local().unwrap().language_server_ids.keys().cloned() + { + let language = languages.iter().find_map(|l| { + let adapter = self + .languages + .lsp_adapters(&l.name()) + .iter() + .find(|adapter| adapter.name == started_lsp_name)? + .clone(); + Some((l, adapter)) }); + if let Some((language, adapter)) = language { + let worktree = self.worktree_for_id(worktree_id, cx).ok(); + let root_file = worktree.as_ref().and_then(|worktree| { + worktree + .update(cx, |tree, cx| tree.root_file(cx)) + .map(|f| f as _) + }); + let settings = language_settings(Some(language.name()), root_file.as_ref(), cx); + if !settings.enable_language_server { + language_servers_to_stop.push((worktree_id, started_lsp_name.clone())); + } else if let Some(worktree) = worktree { + let server_name = &adapter.name; + match ( + current_lsp_settings.get(server_name), + new_lsp_settings.get(server_name), + ) { + (None, None) => {} + (Some(_), None) | (None, Some(_)) => { + language_servers_to_restart.push((worktree, language.name())); + } + (Some(current_lsp_settings), Some(new_lsp_settings)) => { + if current_lsp_settings != new_lsp_settings { + language_servers_to_restart.push((worktree, language.name())); + } + } + } + } + } } - for id in to_stop { - self.stop_local_language_server(id, cx).detach(); + + for (worktree_id, adapter_name) in language_servers_to_stop { + self.stop_local_language_server(worktree_id, adapter_name, cx) + .detach(); } + if let Some(prettier_store) = self.as_local().map(|s| s.prettier_store.clone()) { prettier_store.update(cx, |prettier_store, cx| { prettier_store.on_settings_changed(language_formatters_to_check, cx) }) } + // Start all the newly-enabled language servers. + for (worktree, language) in language_servers_to_start { + self.as_local_mut() + .unwrap() + .start_language_servers(&worktree, language, cx); + } + + // Restart all language servers with changed initialization options. + for (worktree, language) in language_servers_to_restart { + self.restart_local_language_servers(worktree, language, cx); + } + cx.notify(); } @@ -3732,10 +3742,12 @@ impl LspStore { .await }) } else if self.mode.is_local() { - let Some((lsp_adapter, lang_server)) = buffer_handle.update(cx, |buffer, cx| { + let buffer = buffer_handle.read(cx); + let (lsp_adapter, lang_server) = if let Some((adapter, server)) = self.language_server_for_local_buffer(buffer, action.server_id, cx) - .map(|(adapter, server)| (adapter.clone(), server.clone())) - }) else { + { + (adapter.clone(), server.clone()) + } else { return Task::ready(Ok(Default::default())); }; cx.spawn(move |this, mut cx| async move { @@ -3816,16 +3828,19 @@ impl LspStore { } }) } else { - let Some(lang_server) = buffer_handle.update(cx, |buffer, cx| { + let buffer = buffer_handle.read(cx); + let (_, lang_server) = if let Some((adapter, server)) = self.language_server_for_local_buffer(buffer, server_id, cx) - .map(|(_, server)| server.clone()) - }) else { + { + (adapter.clone(), server.clone()) + } else { return Task::ready(Ok(hint)); }; if !InlayHints::can_resolve_inlays(&lang_server.capabilities()) { return Task::ready(Ok(hint)); } - let buffer_snapshot = buffer_handle.read(cx).snapshot(); + + let buffer_snapshot = buffer.snapshot(); cx.spawn(move |_, mut cx| async move { let resolve_task = lang_server.request::( InlayHints::project_to_lsp_hint(hint, &buffer_snapshot), @@ -3858,24 +3873,22 @@ impl LspStore { let Some(server_id) = self .as_local() .and_then(|local| { - buffer.update(cx, |buffer, cx| { - local - .language_servers_for_buffer(buffer, cx) - .filter(|(_, server)| { - server - .capabilities() - .linked_editing_range_provider - .is_some() - }) - .filter(|(adapter, _)| { - scope - .as_ref() - .map(|scope| scope.language_allowed(&adapter.name)) - .unwrap_or(true) - }) - .map(|(_, server)| LanguageServerToQuery::Other(server.server_id())) - .next() - }) + local + .language_servers_for_buffer(buffer.read(cx), cx) + .filter(|(_, server)| { + server + .capabilities() + .linked_editing_range_provider + .is_some() + }) + .filter(|(adapter, _)| { + scope + .as_ref() + .map(|scope| scope.language_allowed(&adapter.name)) + .unwrap_or(true) + }) + .map(|(_, server)| LanguageServerToQuery::Other(server.server_id())) + .next() }) .or_else(|| { self.upstream_client() @@ -4129,19 +4142,17 @@ impl LspStore { let scope = snapshot.language_scope_at(offset); let language = snapshot.language().cloned(); - let server_ids: Vec<_> = buffer.update(cx, |buffer, cx| { - local - .language_servers_for_buffer(buffer, cx) - .filter(|(_, server)| server.capabilities().completion_provider.is_some()) - .filter(|(adapter, _)| { - scope - .as_ref() - .map(|scope| scope.language_allowed(&adapter.name)) - .unwrap_or(true) - }) - .map(|(_, server)| server.server_id()) - .collect() - }); + let server_ids: Vec<_> = local + .language_servers_for_buffer(buffer.read(cx), cx) + .filter(|(_, server)| server.capabilities().completion_provider.is_some()) + .filter(|(adapter, _)| { + scope + .as_ref() + .map(|scope| scope.language_allowed(&adapter.name)) + .unwrap_or(true) + }) + .map(|(_, server)| server.server_id()) + .collect(); let buffer = buffer.clone(); cx.spawn(move |this, mut cx| async move { @@ -4461,9 +4472,10 @@ impl LspStore { push_to_history: bool, cx: &mut ModelContext, ) -> Task>> { + let buffer = buffer_handle.read(cx); + let buffer_id = buffer.remote_id(); + if let Some((client, project_id)) = self.upstream_client() { - let buffer = buffer_handle.read(cx); - let buffer_id = buffer.remote_id(); cx.spawn(move |_, mut cx| async move { let request = { let completion = completions.borrow()[completion_index].clone(); @@ -4502,14 +4514,9 @@ impl LspStore { }) } else { let server_id = completions.borrow()[completion_index].server_id; - let Some(server) = buffer_handle.update(cx, |buffer, cx| { - Some( - self.language_server_for_local_buffer(buffer, server_id, cx)? - .1 - .clone(), - ) - }) else { - return Task::ready(Ok(None)); + let server = match self.language_server_for_local_buffer(buffer, server_id, cx) { + Some((_, server)) => server.clone(), + _ => return Task::ready(Ok(None)), }; let snapshot = buffer_handle.read(&cx).snapshot(); @@ -4800,7 +4807,6 @@ impl LspStore { }) } else if let Some(local) = self.as_local() { struct WorkspaceSymbolsResult { - server_id: LanguageServerId, lsp_adapter: Arc, worktree: WeakModel, worktree_abs_path: Arc, @@ -4808,8 +4814,7 @@ impl LspStore { } let mut requests = Vec::new(); - let mut requested_servers = BTreeSet::new(); - 'next_server: for ((worktree_id, _), server_ids) in local.language_server_ids.iter() { + for ((worktree_id, _), server_id) in local.language_server_ids.iter() { let Some(worktree_handle) = self .worktree_store .read(cx) @@ -4821,63 +4826,55 @@ impl LspStore { if !worktree.is_visible() { continue; } + let worktree_abs_path = worktree.abs_path().clone(); - let mut servers_to_query = server_ids - .difference(&requested_servers) - .cloned() - .collect::>(); - for server_id in &servers_to_query { - let (lsp_adapter, server) = match local.language_servers.get(server_id) { - Some(LanguageServerState::Running { - adapter, server, .. - }) => (adapter.clone(), server), + let (lsp_adapter, server) = match local.language_servers.get(server_id) { + Some(LanguageServerState::Running { + adapter, server, .. + }) => (adapter.clone(), server), - _ => continue 'next_server, - }; - let worktree_abs_path = worktree.abs_path().clone(); - let worktree_handle = worktree_handle.clone(); - let server_id = server.server_id(); - requests.push( - server - .request::( - lsp::WorkspaceSymbolParams { - query: query.to_string(), - ..Default::default() - }, - ) - .log_err() - .map(move |response| { - let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response { - lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { - flat_responses.into_iter().map(|lsp_symbol| { + _ => continue, + }; + + requests.push( + server + .request::( + lsp::WorkspaceSymbolParams { + query: query.to_string(), + ..Default::default() + }, + ) + .log_err() + .map(move |response| { + let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response { + lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { + flat_responses.into_iter().map(|lsp_symbol| { (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) - }).collect::>() - } - lsp::WorkspaceSymbolResponse::Nested(nested_responses) => { - nested_responses.into_iter().filter_map(|lsp_symbol| { - let location = match lsp_symbol.location { - OneOf::Left(location) => location, - OneOf::Right(_) => { - log::error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport"); - return None - } - }; - Some((lsp_symbol.name, lsp_symbol.kind, location)) - }).collect::>() - } - }).unwrap_or_default(); - - WorkspaceSymbolsResult { - server_id, - lsp_adapter, - worktree: worktree_handle.downgrade(), - worktree_abs_path, - lsp_symbols, + }).collect::>() } - }), - ); - } - requested_servers.append(&mut servers_to_query); + lsp::WorkspaceSymbolResponse::Nested(nested_responses) => { + nested_responses.into_iter().filter_map(|lsp_symbol| { + let location = match lsp_symbol.location { + OneOf::Left(location) => location, + OneOf::Right(_) => { + log::error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport"); + return None + } + }; + Some((lsp_symbol.name, lsp_symbol.kind, location)) + }).collect::>() + } + }).unwrap_or_default(); + + WorkspaceSymbolsResult { + lsp_adapter, + + worktree: worktree_handle.downgrade(), + worktree_abs_path, + lsp_symbols, + } + }), + ); } cx.spawn(move |this, mut cx| async move { @@ -4917,7 +4914,6 @@ impl LspStore { }; let signature = this.symbol_signature(&project_path); Some(CoreSymbol { - source_language_server_id: result.server_id, language_server_name: result.lsp_adapter.name.clone(), source_worktree_id, path: project_path, @@ -4997,19 +4993,19 @@ impl LspStore { buffer: Model, cx: &mut ModelContext, ) -> Option<()> { - let language_servers: Vec<_> = buffer.update(cx, |buffer, cx| { - Some( - self.as_local()? - .language_servers_for_buffer(buffer, cx) - .map(|i| i.1.clone()) - .collect(), - ) - })?; let buffer = buffer.read(cx); let file = File::from_dyn(buffer.file())?; let abs_path = file.as_local()?.abs_path(cx); let uri = lsp::Url::from_file_path(abs_path).unwrap(); let next_snapshot = buffer.text_snapshot(); + + let language_servers: Vec<_> = self + .as_local() + .unwrap() + .language_servers_for_buffer(buffer, cx) + .map(|i| i.1.clone()) + .collect(); + for language_server in language_servers { let language_server = language_server.clone(); @@ -5126,10 +5122,7 @@ impl LspStore { } } - let language_servers = buffer.update(cx, |buffer, cx| { - local.language_server_ids_for_buffer(buffer, cx) - }); - for language_server_id in language_servers { + for language_server_id in local.language_server_ids_for_buffer(buffer.read(cx), cx) { self.simulate_disk_based_diagnostics_events_if_needed(language_server_id, cx); } @@ -5149,37 +5142,31 @@ impl LspStore { local .language_server_ids .iter() - .flat_map(|((worktree_id, _), server_ids)| { + .filter_map(|((worktree_id, _), server_id)| { let worktree = this .worktree_store .read(cx) - .worktree_for_id(*worktree_id, cx); - let delegate = worktree.map(|worktree| { - LocalLspAdapterDelegate::new( - local.languages.clone(), - &local.environment, - cx.weak_model(), - &worktree, - local.http_client.clone(), - local.fs.clone(), - cx, - ) - }); - - server_ids.iter().filter_map(move |server_id| { - let states = local.language_servers.get(server_id)?; - - match states { - LanguageServerState::Starting { .. } => None, - LanguageServerState::Running { - adapter, server, .. - } => Some(( - adapter.adapter.clone(), - server.clone(), - delegate.clone()? as Arc, - )), - } - }) + .worktree_for_id(*worktree_id, cx)?; + let state = local.language_servers.get(server_id)?; + let delegate = LocalLspAdapterDelegate::new( + local.languages.clone(), + &local.environment, + cx.weak_model(), + &worktree, + local.http_client.clone(), + local.fs.clone(), + cx, + ); + match state { + LanguageServerState::Starting(_) => None, + LanguageServerState::Running { + adapter, server, .. + } => Some(( + adapter.adapter.clone(), + server.clone(), + delegate as Arc, + )), + } }) .collect::>() }) @@ -5237,31 +5224,27 @@ impl LspStore { pub(crate) fn language_servers_for_local_buffer<'a>( &'a self, - buffer: &Buffer, - cx: &mut AppContext, + buffer: &'a Buffer, + cx: &'a AppContext, ) -> impl Iterator, &'a Arc)> { - let local = self.as_local(); - let language_server_ids = local - .map(|local| local.language_server_ids_for_buffer(buffer, cx)) - .unwrap_or_default(); - - language_server_ids - .into_iter() - .filter_map( - move |server_id| match local?.language_servers.get(&server_id)? { + self.as_local().into_iter().flat_map(|local| { + local + .language_server_ids_for_buffer(buffer, cx) + .into_iter() + .filter_map(|server_id| match local.language_servers.get(&server_id)? { LanguageServerState::Running { adapter, server, .. } => Some((adapter, server)), _ => None, - }, - ) + }) + }) } pub fn language_server_for_local_buffer<'a>( &'a self, buffer: &'a Buffer, server_id: LanguageServerId, - cx: &'a mut AppContext, + cx: &'a AppContext, ) -> Option<(&'a Arc, &'a Arc)> { self.as_local()? .language_servers_for_buffer(buffer, cx) @@ -5270,11 +5253,39 @@ impl LspStore { fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext) { self.diagnostic_summaries.remove(&id_to_remove); + let to_remove = Vec::new(); if let Some(local) = self.as_local_mut() { - let to_remove = local.remove_worktree(id_to_remove, cx); - for server in to_remove { - self.language_server_statuses.remove(&server); + local.diagnostics.remove(&id_to_remove); + local.prettier_store.update(cx, |prettier_store, cx| { + prettier_store.remove_worktree(id_to_remove, cx); + }); + + let mut servers_to_remove = HashMap::default(); + let mut servers_to_preserve = HashSet::default(); + for ((worktree_id, server_name), &server_id) in &local.language_server_ids { + if worktree_id == &id_to_remove { + servers_to_remove.insert(server_id, server_name.clone()); + } else { + servers_to_preserve.insert(server_id); + } } + servers_to_remove.retain(|server_id, _| !servers_to_preserve.contains(server_id)); + for (server_id_to_remove, server_name) in servers_to_remove { + local + .language_server_ids + .remove(&(id_to_remove, server_name)); + local + .language_server_watched_paths + .remove(&server_id_to_remove); + local + .last_workspace_edits_by_language_server + .remove(&server_id_to_remove); + local.language_servers.remove(&server_id_to_remove); + cx.emit(LspStoreEvent::LanguageServerRemoved(server_id_to_remove)); + } + } + for server in to_remove { + self.language_server_statuses.remove(&server); } } @@ -5342,9 +5353,7 @@ impl LspStore { self.as_local_mut() .unwrap() .language_server_ids - .entry((worktree_id, language_server_name)) - .or_default() - .insert(language_server_id); + .insert((worktree_id, language_server_name), language_server_id); } pub fn update_diagnostic_entries( @@ -5480,17 +5489,10 @@ impl LspStore { .await }) } else if let Some(local) = self.as_local() { - let Some(language_server_id) = local - .language_server_ids - .get(&( - symbol.source_worktree_id, - symbol.language_server_name.clone(), - )) - .and_then(|ids| { - ids.contains(&symbol.source_language_server_id) - .then_some(symbol.source_language_server_id) - }) - else { + let Some(&language_server_id) = local.language_server_ids.get(&( + symbol.source_worktree_id, + symbol.language_server_name.clone(), + )) else { return Task::ready(Err(anyhow!( "language server for worktree and language not found" ))); @@ -5631,19 +5633,16 @@ impl LspStore { let snapshot = buffer.read(cx).snapshot(); let scope = position.and_then(|position| snapshot.language_scope_at(position)); - - let server_ids = buffer.update(cx, |buffer, cx| { - local - .language_servers_for_buffer(buffer, cx) - .filter(|(adapter, _)| { - scope - .as_ref() - .map(|scope| scope.language_allowed(&adapter.name)) - .unwrap_or(true) - }) - .map(|(_, server)| server.server_id()) - .collect::>() - }); + let server_ids = local + .language_servers_for_buffer(buffer.read(cx), cx) + .filter(|(adapter, _)| { + scope + .as_ref() + .map(|scope| scope.language_allowed(&adapter.name)) + .unwrap_or(true) + }) + .map(|(_, server)| server.server_id()) + .collect::>(); let mut response_results = server_ids .into_iter() @@ -6387,13 +6386,18 @@ impl LspStore { } pub fn language_server_for_id(&self, id: LanguageServerId) -> Option> { - let local_lsp_store = self.as_local()?; - if let Some(LanguageServerState::Running { server, .. }) = - local_lsp_store.language_servers.get(&id) - { - Some(server.clone()) - } else if let Some((_, server)) = local_lsp_store.supplementary_language_servers.get(&id) { - Some(Arc::clone(server)) + if let Some(local_lsp_store) = self.as_local() { + if let Some(LanguageServerState::Running { server, .. }) = + local_lsp_store.language_servers.get(&id) + { + Some(server.clone()) + } else if let Some((_, server)) = + local_lsp_store.supplementary_language_servers.get(&id) + { + Some(Arc::clone(server)) + } else { + None + } } else { None } @@ -6790,7 +6794,6 @@ impl LspStore { &Symbol { language_server_name: symbol.language_server_name, source_worktree_id: symbol.source_worktree_id, - source_language_server_id: symbol.source_language_server_id, path: symbol.path, name: symbol.name, kind: symbol.kind, @@ -7116,14 +7119,14 @@ impl LspStore { cx: AsyncAppContext, ) { let server = match server_state { - Some(LanguageServerState::Starting { startup, .. }) => { + Some(LanguageServerState::Starting(task)) => { let mut timer = cx .background_executor() .timer(SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT) .fuse(); select! { - server = startup.fuse() => server, + server = task.fuse() => server, _ = timer => { log::info!( "timeout waiting for language server {} to finish launching before stopping", @@ -7150,44 +7153,37 @@ impl LspStore { // for the stopped server fn stop_local_language_server( &mut self, - server_id: LanguageServerId, + worktree_id: WorktreeId, + adapter_name: LanguageServerName, cx: &mut ModelContext, ) -> Task> { + let key = (worktree_id, adapter_name); let local = match &mut self.mode { LspStoreMode::Local(local) => local, _ => { return Task::ready(Vec::new()); } }; - - let mut orphaned_worktrees = vec![]; - // Did any of the worktrees reference this server ID at least once? - let mut was_referenced = false; - // Remove this server ID from all entries in the given worktree. - local.language_server_ids.retain(|(worktree, _), ids| { - if !ids.remove(&server_id) { - return true; - } - - was_referenced = true; - if ids.is_empty() { - orphaned_worktrees.push(*worktree); - false - } else { - true - } - }); - let Some(status) = self - .language_server_statuses - .remove(&server_id) - .filter(|_| was_referenced) - else { + let Some(server_id) = local.language_server_ids.remove(&key) else { return Task::ready(Vec::new()); }; - - let name = LanguageServerName(status.name.into()); + let name = key.1; log::info!("stopping language server {name}"); + // Remove other entries for this language server as well + let mut orphaned_worktrees = vec![worktree_id]; + let other_keys = local + .language_server_ids + .keys() + .cloned() + .collect::>(); + for other_key in other_keys { + if local.language_server_ids.get(&other_key) == Some(&server_id) { + local.language_server_ids.remove(&other_key); + orphaned_worktrees.push(other_key.0); + } + } + self.buffer_store.update(cx, |buffer_store, cx| { for buffer in buffer_store.buffers() { buffer.update(cx, |buffer, cx| { @@ -7221,6 +7217,7 @@ impl LspStore { }); } + self.language_server_statuses.remove(&server_id); let local = self.as_local_mut().unwrap(); for diagnostics in local.diagnostics.values_mut() { diagnostics.retain(|_, diagnostics_by_server_id| { @@ -7259,91 +7256,83 @@ impl LspStore { .spawn(request) .detach_and_log_err(cx); } else { - let Some(local) = self.as_local_mut() else { - return; - }; - let language_servers_for_worktrees = buffers + let language_server_lookup_info: HashSet<(Model, LanguageName)> = buffers .into_iter() .filter_map(|buffer| { - buffer.update(cx, |buffer, cx| { - let worktree_id = buffer.file()?.worktree_id(cx); - let language_server_ids = local.language_servers_for_buffer(buffer, cx); - Some(( - language_server_ids - .filter_map(|(adapter, server)| { - let new_adapter = - local.languages.adapter_for_name(&adapter.name()); - new_adapter.map(|adapter| (server.server_id(), adapter)) - }) - .collect::>(), - worktree_id, - )) - }) + let buffer = buffer.read(cx); + let file = buffer.file()?; + let worktree = File::from_dyn(Some(file))?.worktree.clone(); + let language = + self.languages + .language_for_file(file, Some(buffer.as_rope()), cx)?; + + Some((worktree, language.name())) }) - .fold( - HashMap::default(), - |mut worktree_to_ids, (server_ids, worktree_id)| { - worktree_to_ids - .entry(worktree_id) - .or_insert_with(BTreeMap::new) - .extend(server_ids); - worktree_to_ids - }, - ); + .collect(); - // Multiple worktrees might refer to the same language server; - // we don't want to restart them multiple times. - let mut restarted_language_servers = BTreeMap::new(); - let mut servers_to_stop = BTreeSet::new(); - local.lsp_tree.clone().update(cx, |tree, cx| { - for (worktree_id, adapters_to_restart) in language_servers_for_worktrees { - let Some(worktree_handle) = local - .worktree_store - .read(cx) - .worktree_for_id(worktree_id, cx) - else { - continue; - }; - let delegate = - LocalLspAdapterDelegate::from_local_lsp(local, &worktree_handle, cx); - let servers_to_restart = adapters_to_restart.keys().copied().collect(); - tree.restart_language_servers( - worktree_id, - servers_to_restart, - &mut |old_server_id, disposition| match restarted_language_servers - .entry(old_server_id) - { - btree_map::Entry::Vacant(unfilled) => { - servers_to_stop.insert(old_server_id); - - let adapter = adapters_to_restart - .get(&old_server_id) - .map(Clone::clone) - .expect("Language server adapter to be found"); - let new_id = local.start_language_server( - &worktree_handle, - delegate.clone(), - adapter, - disposition.settings, - cx, - ); - unfilled.insert(new_id); - new_id - } - btree_map::Entry::Occupied(server_id) => *server_id.get(), - }, - ); - } - }); - for server_id in servers_to_stop { - self.stop_local_language_server(server_id, cx).detach(); + for (worktree, language) in language_server_lookup_info { + self.restart_local_language_servers(worktree, language, cx); } - // for (worktree, language) in language_server_lookup_info { - // self.restart_local_language_servers(worktree, language, cx); - // } } } + fn restart_local_language_servers( + &mut self, + worktree: Model, + language: LanguageName, + cx: &mut ModelContext, + ) { + let worktree_id = worktree.read(cx).id(); + + let stop_tasks = self + .languages + .clone() + .lsp_adapters(&language) + .iter() + .map(|adapter| { + let stop_task = + self.stop_local_language_server(worktree_id, adapter.name.clone(), cx); + (stop_task, adapter.name.clone()) + }) + .collect::>(); + if stop_tasks.is_empty() { + return; + } + + cx.spawn(move |this, mut cx| async move { + // For each stopped language server, record all of the worktrees with which + // it was associated. + let mut affected_worktrees = Vec::new(); + for (stop_task, language_server_name) in stop_tasks { + for affected_worktree_id in stop_task.await { + affected_worktrees.push((affected_worktree_id, language_server_name.clone())); + } + } + + this.update(&mut cx, |this, cx| { + let local = this.as_local_mut().unwrap(); + // Restart the language server for the given worktree. + // + local.start_language_servers(&worktree, language.clone(), cx); + + // Lookup new server ids and set them for each of the orphaned worktrees + for (affected_worktree_id, language_server_name) in affected_worktrees { + if let Some(new_server_id) = local + .language_server_ids + .get(&(worktree_id, language_server_name.clone())) + .cloned() + { + local + .language_server_ids + .insert((affected_worktree_id, language_server_name), new_server_id); + } + } + }) + .ok(); + }) + .detach(); + } + pub fn update_diagnostics( &mut self, language_server_id: LanguageServerId, @@ -7469,14 +7458,12 @@ impl LspStore { Ok(()) } - #[allow(clippy::too_many_arguments)] fn insert_newly_running_language_server( &mut self, adapter: Arc, language_server: Arc, server_id: LanguageServerId, key: (WorktreeId, LanguageServerName), - workspace_folders: Arc>>, cx: &mut ModelContext, ) { let Some(local) = self.as_local_mut() else { @@ -7487,7 +7474,7 @@ impl LspStore { if local .language_server_ids .get(&key) - .map(|ids| !ids.contains(&server_id)) + .map(|id| id != &server_id) .unwrap_or(false) { return; @@ -7497,12 +7484,11 @@ impl LspStore { // indicating that the server is up and running and ready local.language_servers.insert( server_id, - LanguageServerState::running( - workspace_folders.lock().clone(), - adapter.clone(), - language_server.clone(), - None, - ), + LanguageServerState::Running { + adapter: adapter.clone(), + server: language_server.clone(), + simulate_disk_based_diagnostics_completion: None, + }, ); if let Some(file_ops_caps) = language_server .capabilities() @@ -7582,29 +7568,36 @@ impl LspStore { let local = self.as_local_mut().unwrap(); - let versions = local - .buffer_snapshots - .entry(buffer.remote_id()) - .or_default() - .entry(server_id) - .or_insert_with(|| { - vec![LspBufferSnapshot { - version: 0, - snapshot: buffer.text_snapshot(), - }] - }); + if local.registered_buffers.contains_key(&buffer.remote_id()) { + let versions = local + .buffer_snapshots + .entry(buffer.remote_id()) + .or_default() + .entry(server_id) + .or_insert_with(|| { + vec![LspBufferSnapshot { + version: 0, + snapshot: buffer.text_snapshot(), + }] + }); - let snapshot = versions.last().unwrap(); - let version = snapshot.version; - let initial_snapshot = &snapshot.snapshot; - let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); - language_server.register_buffer( - buffer.remote_id(), - uri, - adapter.language_id(&language.name()), - version, - initial_snapshot.text(), - ); + let snapshot = versions.last().unwrap(); + let version = snapshot.version; + let initial_snapshot = &snapshot.snapshot; + let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + language_server + .notify::( + &lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( + uri, + adapter.language_id(&language.name()), + version, + initial_snapshot.text(), + ), + }, + ) + .log_err(); + } buffer_handle.update(cx, |buffer, cx| { buffer.set_completion_triggers( @@ -7667,11 +7660,12 @@ impl LspStore { let servers = buffers .into_iter() .flat_map(|buffer| { - buffer.update(cx, |buffer, cx| { - local.language_server_ids_for_buffer(buffer, cx).into_iter() - }) + local + .language_server_ids_for_buffer(buffer.read(cx), cx) + .into_iter() }) .collect::>(); + for server_id in servers { self.cancel_language_server_work(server_id, None, cx); } @@ -7704,6 +7698,16 @@ impl LspStore { ) .ok(); } + + if progress.is_cancellable { + server + .notify::( + &WorkDoneProgressCancelParams { + token: lsp::NumberOrString::String(token.clone()), + }, + ) + .ok(); + } } } } else if let Some((client, project_id)) = self.upstream_client() { @@ -7750,7 +7754,7 @@ impl LspStore { } } - pub(crate) fn supplementary_language_servers( + pub fn supplementary_language_servers( &self, ) -> impl '_ + Iterator { self.as_local().into_iter().flat_map(|local| { @@ -7793,10 +7797,8 @@ impl LspStore { let mut language_server_ids = local .language_server_ids .iter() - .flat_map(|((server_worktree, _), server_ids)| { - server_ids - .iter() - .filter_map(|server_id| server_worktree.eq(&worktree_id).then(|| *server_id)) + .filter_map(|((server_worktree_id, _), server_id)| { + (*server_worktree_id == worktree_id).then_some(*server_id) }) .collect::>(); language_server_ids.sort(); @@ -7857,7 +7859,6 @@ impl LspStore { proto::Symbol { language_server_name: symbol.language_server_name.0.to_string(), source_worktree_id: symbol.source_worktree_id.to_proto(), - language_server_id: symbol.source_language_server_id.to_proto(), worktree_id: symbol.path.worktree_id.to_proto(), path: symbol.path.path.to_string_lossy().to_string(), name: symbol.name.clone(), @@ -7892,9 +7893,6 @@ impl LspStore { Ok(CoreSymbol { language_server_name: LanguageServerName(serialized_symbol.language_server_name.into()), source_worktree_id, - source_language_server_id: LanguageServerId::from_proto( - serialized_symbol.language_server_id, - ), path, name: serialized_symbol.name, range: Unclipped(PointUtf16::new(start.row, start.column)) @@ -8291,11 +8289,7 @@ impl LanguageServerLogType { } pub enum LanguageServerState { - Starting { - startup: Task>>, - /// List of language servers that will be added to the workspace once it's initialization completes. - pending_workspace_folders: Arc>>, - }, + Starting(Task>>), Running { adapter: Arc, @@ -8304,50 +8298,10 @@ pub enum LanguageServerState { }, } -impl LanguageServerState { - fn add_workspace_folder(&self, uri: Url) { - match self { - LanguageServerState::Starting { - pending_workspace_folders, - .. - } => { - pending_workspace_folders.lock().insert(uri); - } - LanguageServerState::Running { server, .. } => { - server.add_workspace_folder(uri); - } - } - } - fn _remove_workspace_folder(&self, uri: Url) { - match self { - LanguageServerState::Starting { - pending_workspace_folders, - .. - } => { - pending_workspace_folders.lock().remove(&uri); - } - LanguageServerState::Running { server, .. } => server.remove_workspace_folder(uri), - } - } - fn running( - workspace_folders: BTreeSet, - adapter: Arc, - server: Arc, - simulate_disk_based_diagnostics_completion: Option>, - ) -> Self { - server.set_workspace_folders(workspace_folders); - Self::Running { - adapter, - server, - simulate_disk_based_diagnostics_completion, - } - } -} - impl std::fmt::Debug for LanguageServerState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - LanguageServerState::Starting { .. } => { + LanguageServerState::Starting(_) => { f.debug_struct("LanguageServerState::Starting").finish() } LanguageServerState::Running { .. } => { @@ -8501,27 +8455,20 @@ impl LspAdapter for SshLspAdapter { } } -pub fn language_server_settings<'a>( +pub fn language_server_settings<'a, 'b: 'a>( delegate: &'a dyn LspAdapterDelegate, language: &LanguageServerName, - cx: &'a AppContext, + cx: &'b AppContext, ) -> Option<&'a LspSettings> { - language_server_settings_for( - SettingsLocation { + ProjectSettings::get( + Some(SettingsLocation { worktree_id: delegate.worktree_id(), path: delegate.worktree_root_path(), - }, - language, + }), cx, ) -} - -pub(crate) fn language_server_settings_for<'a>( - location: SettingsLocation<'a>, - language: &LanguageServerName, - cx: &'a AppContext, -) -> Option<&'a LspSettings> { - ProjectSettings::get(Some(location), cx).lsp.get(language) + .lsp + .get(language) } pub struct LocalLspAdapterDelegate { @@ -8541,13 +8488,10 @@ impl LocalLspAdapterDelegate { worktree: &Model, http_client: Arc, fs: Arc, - cx: &mut AppContext, + cx: &mut ModelContext, ) -> Arc { - let (worktree_id, worktree_abs_path) = { - let worktree = worktree.read(cx); - (worktree.id(), worktree.abs_path()) - }; - + let worktree_id = worktree.read(cx).id(); + let worktree_abs_path = worktree.read(cx).abs_path(); let load_shell_env_task = environment.update(cx, |env, cx| { env.get_environment(Some(worktree_id), Some(worktree_abs_path), cx) }); @@ -8561,22 +8505,6 @@ impl LocalLspAdapterDelegate { load_shell_env_task, }) } - - fn from_local_lsp( - local: &LocalLspStore, - worktree: &Model, - cx: &mut AppContext, - ) -> Arc { - Self::new( - local.languages.clone(), - &local.environment, - local.weak.clone(), - worktree, - local.http_client.clone(), - local.fs.clone(), - cx, - ) - } } #[async_trait] @@ -8781,7 +8709,6 @@ async fn populate_labels_for_symbols( output.push(Symbol { language_server_name: symbol.language_server_name, source_worktree_id: symbol.source_worktree_id, - source_language_server_id: symbol.source_language_server_id, path: symbol.path, label: label.unwrap_or_else(|| CodeLabel::plain(name.clone(), None)), name, diff --git a/crates/project/src/prettier_store.rs b/crates/project/src/prettier_store.rs index 36da95422d..e707f9e9bc 100644 --- a/crates/project/src/prettier_store.rs +++ b/crates/project/src/prettier_store.rs @@ -40,7 +40,7 @@ pub struct PrettierStore { prettier_instances: HashMap, } -pub(crate) enum PrettierStoreEvent { +pub enum PrettierStoreEvent { LanguageServerRemoved(LanguageServerId), LanguageServerAdded { new_server_id: LanguageServerId, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d4f78757d8..78965f64b5 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -9,7 +9,6 @@ pub mod lsp_ext_command; pub mod lsp_store; pub mod prettier_store; pub mod project_settings; -mod project_tree; pub mod search; mod task_inventory; pub mod task_store; @@ -475,7 +474,6 @@ pub struct DocumentHighlight { pub struct Symbol { pub language_server_name: LanguageServerName, pub source_worktree_id: WorktreeId, - pub source_language_server_id: LanguageServerId, pub path: ProjectPath, pub label: CodeLabel, pub name: String, @@ -1892,7 +1890,7 @@ impl Project { pub fn open_buffer( &mut self, path: impl Into, - cx: &mut AppContext, + cx: &mut ModelContext, ) -> Task>> { if self.is_disconnected(cx) { return Task::ready(Err(anyhow!(ErrorCode::Disconnected))); @@ -1907,11 +1905,11 @@ impl Project { pub fn open_buffer_with_lsp( &mut self, path: impl Into, - cx: &mut AppContext, + cx: &mut ModelContext, ) -> Task, lsp_store::OpenLspBufferHandle)>> { let buffer = self.open_buffer(path, cx); let lsp_store = self.lsp_store().clone(); - cx.spawn(|mut cx| async move { + cx.spawn(|_, mut cx| async move { let buffer = buffer.await?; let handle = lsp_store.update(&mut cx, |lsp_store, cx| { lsp_store.register_buffer_with_language_servers(&buffer, cx) @@ -4147,25 +4145,14 @@ impl Project { self.lsp_store.read(cx).supplementary_language_servers() } - pub fn language_server_for_id( - &self, - id: LanguageServerId, - cx: &AppContext, - ) -> Option> { - self.lsp_store.read(cx).language_server_for_id(id) - } - - pub fn for_language_servers_for_local_buffer( - &self, - buffer: &Buffer, - callback: impl FnOnce( - Box, &Arc)> + '_>, - ) -> R, - cx: &mut AppContext, - ) -> R { - self.lsp_store.update(cx, |this, cx| { - callback(Box::new(this.language_servers_for_local_buffer(buffer, cx))) - }) + pub fn language_servers_for_local_buffer<'a>( + &'a self, + buffer: &'a Buffer, + cx: &'a AppContext, + ) -> impl Iterator, &'a Arc)> { + self.lsp_store + .read(cx) + .language_servers_for_local_buffer(buffer, cx) } pub fn buffer_store(&self) -> &Model { diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 1ed99b50cf..fe0bf8e0fa 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1749,12 +1749,6 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) { }); }) }); - let _rs_buffer = project - .update(cx, |project, cx| { - project.open_local_buffer_with_lsp("/dir/a.rs", cx) - }) - .await - .unwrap(); let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap(); assert_eq!( fake_rust_server_2 @@ -2579,28 +2573,25 @@ async fn test_definition(cx: &mut gpui::TestAppContext) { fs.insert_tree( "/dir", json!({ + "a.rs": "const fn a() { A }", "b.rs": "const y: i32 = crate::a()", }), ) .await; - fs.insert_tree( - "/another_dir", - json!({ - "a.rs": "const fn a() { A }"}), - ) - .await; - let project = Project::test(fs, ["/dir".as_ref()], cx).await; + let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await; let language_registry = project.read_with(cx, |project, _| project.languages().clone()); language_registry.add(rust_lang()); let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default()); + let (buffer, _handle) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp("/dir/b.rs", cx) }) .await .unwrap(); + let fake_server = fake_servers.next().await.unwrap(); fake_server.handle_request::(|params, _| async move { let params = params.text_document_position_params; @@ -2612,11 +2603,12 @@ async fn test_definition(cx: &mut gpui::TestAppContext) { Ok(Some(lsp::GotoDefinitionResponse::Scalar( lsp::Location::new( - lsp::Url::from_file_path("/another_dir/a.rs").unwrap(), + lsp::Url::from_file_path("/dir/a.rs").unwrap(), lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), ), ))) }); + let mut definitions = project .update(cx, |project, cx| project.definition(&buffer, 22, cx)) .await @@ -2637,21 +2629,18 @@ async fn test_definition(cx: &mut gpui::TestAppContext) { .as_local() .unwrap() .abs_path(cx), - Path::new("/another_dir/a.rs"), + Path::new("/dir/a.rs"), ); assert_eq!(definition.target.range.to_offset(target_buffer), 9..10); assert_eq!( list_worktrees(&project, cx), - [ - ("/another_dir/a.rs".as_ref(), false), - ("/dir".as_ref(), true) - ], + [("/dir/a.rs".as_ref(), false), ("/dir/b.rs".as_ref(), true)], ); drop(definition); }); cx.update(|cx| { - assert_eq!(list_worktrees(&project, cx), [("/dir".as_ref(), true)]); + assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]); }); fn list_worktrees<'a>( diff --git a/crates/project/src/project_tree.rs b/crates/project/src/project_tree.rs deleted file mode 100644 index 2a172711f4..0000000000 --- a/crates/project/src/project_tree.rs +++ /dev/null @@ -1,243 +0,0 @@ -//! This module defines a Project Tree. -//! -//! A Project Tree is responsible for determining where the roots of subprojects are located in a project. - -mod path_trie; -mod server_tree; - -use std::{ - borrow::Borrow, - collections::{hash_map::Entry, BTreeMap}, - ops::ControlFlow, - sync::Arc, -}; - -use collections::HashMap; -use gpui::{AppContext, Context as _, EventEmitter, Model, ModelContext, Subscription}; -use language::{CachedLspAdapter, LspAdapterDelegate}; -use lsp::LanguageServerName; -use path_trie::{LabelPresence, RootPathTrie, TriePath}; -use settings::{SettingsStore, WorktreeId}; -use worktree::{Event as WorktreeEvent, Worktree}; - -use crate::{ - worktree_store::{WorktreeStore, WorktreeStoreEvent}, - ProjectPath, -}; - -pub(crate) use server_tree::{LanguageServerTree, LaunchDisposition}; - -struct WorktreeRoots { - roots: RootPathTrie, - worktree_store: Model, - _worktree_subscription: Subscription, -} - -impl WorktreeRoots { - fn new( - worktree_store: Model, - worktree: Model, - cx: &mut AppContext, - ) -> Model { - cx.new_model(|cx| Self { - roots: RootPathTrie::new(), - worktree_store, - _worktree_subscription: cx.subscribe(&worktree, |this: &mut Self, _, event, cx| { - match event { - WorktreeEvent::UpdatedEntries(changes) => { - for (path, _, kind) in changes.iter() { - match kind { - worktree::PathChange::Removed => { - let path = TriePath::from(path.as_ref()); - this.roots.remove(&path); - } - _ => {} - } - } - } - WorktreeEvent::UpdatedGitRepositories(_) => {} - WorktreeEvent::DeletedEntry(entry_id) => { - let Some(entry) = this.worktree_store.read(cx).entry_for_id(*entry_id, cx) - else { - return; - }; - let path = TriePath::from(entry.path.as_ref()); - this.roots.remove(&path); - } - } - }), - }) - } -} - -pub struct ProjectTree { - root_points: HashMap>, - worktree_store: Model, - _subscriptions: [Subscription; 2], -} - -#[derive(Debug, Clone)] -struct AdapterWrapper(Arc); -impl PartialEq for AdapterWrapper { - fn eq(&self, other: &Self) -> bool { - self.0.name.eq(&other.0.name) - } -} - -impl Eq for AdapterWrapper {} - -impl std::hash::Hash for AdapterWrapper { - fn hash(&self, state: &mut H) { - self.0.name.hash(state); - } -} - -impl PartialOrd for AdapterWrapper { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.0.name.cmp(&other.0.name)) - } -} - -impl Ord for AdapterWrapper { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.name.cmp(&other.0.name) - } -} - -impl Borrow for AdapterWrapper { - fn borrow(&self) -> &LanguageServerName { - &self.0.name - } -} - -#[derive(PartialEq)] -pub(crate) enum ProjectTreeEvent { - WorktreeRemoved(WorktreeId), - Cleared, -} - -impl EventEmitter for ProjectTree {} - -impl ProjectTree { - pub(crate) fn new(worktree_store: Model, cx: &mut AppContext) -> Model { - cx.new_model(|cx| Self { - root_points: Default::default(), - _subscriptions: [ - cx.subscribe(&worktree_store, Self::on_worktree_store_event), - cx.observe_global::(|this, cx| { - for (_, roots) in &mut this.root_points { - roots.update(cx, |worktree_roots, _| { - worktree_roots.roots = RootPathTrie::new(); - }) - } - cx.emit(ProjectTreeEvent::Cleared); - }), - ], - worktree_store, - }) - } - #[allow(clippy::mutable_key_type)] - fn root_for_path( - &mut self, - ProjectPath { worktree_id, path }: ProjectPath, - adapters: Vec>, - delegate: Arc, - cx: &mut AppContext, - ) -> BTreeMap { - debug_assert_eq!(delegate.worktree_id(), worktree_id); - #[allow(clippy::mutable_key_type)] - let mut roots = BTreeMap::from_iter( - adapters - .into_iter() - .map(|adapter| (AdapterWrapper(adapter), (None, LabelPresence::KnownAbsent))), - ); - let worktree_roots = match self.root_points.entry(worktree_id) { - Entry::Occupied(occupied_entry) => occupied_entry.get().clone(), - Entry::Vacant(vacant_entry) => { - let Some(worktree) = self - .worktree_store - .read(cx) - .worktree_for_id(worktree_id, cx) - else { - return Default::default(); - }; - let roots = WorktreeRoots::new(self.worktree_store.clone(), worktree, cx); - vacant_entry.insert(roots).clone() - } - }; - - let key = TriePath::from(&*path); - worktree_roots.update(cx, |this, _| { - this.roots.walk(&key, &mut |path, labels| { - for (label, presence) in labels { - if let Some((marked_path, current_presence)) = roots.get_mut(label) { - if *current_presence > *presence { - debug_assert!(false, "RootPathTrie precondition violation; while walking the tree label presence is only allowed to increase"); - } - *marked_path = Some(ProjectPath {worktree_id, path: path.clone()}); - *current_presence = *presence; - } - - } - ControlFlow::Continue(()) - }); - }); - for (adapter, (root_path, presence)) in &mut roots { - if *presence == LabelPresence::Present { - continue; - } - - let depth = root_path - .as_ref() - .map(|root_path| { - path.strip_prefix(&root_path.path) - .unwrap() - .components() - .count() - }) - .unwrap_or_else(|| path.components().count() + 1); - - if depth > 0 { - let root = adapter.0.find_project_root(&path, depth, &delegate); - match root { - Some(known_root) => worktree_roots.update(cx, |this, _| { - let root = TriePath::from(&*known_root); - this.roots - .insert(&root, adapter.0.name(), LabelPresence::Present); - *presence = LabelPresence::Present; - *root_path = Some(ProjectPath { - worktree_id, - path: known_root, - }); - }), - None => worktree_roots.update(cx, |this, _| { - this.roots - .insert(&key, adapter.0.name(), LabelPresence::KnownAbsent); - }), - } - } - } - - roots - .into_iter() - .filter_map(|(k, (path, presence))| { - let path = path?; - presence.eq(&LabelPresence::Present).then(|| (k, path)) - }) - .collect() - } - fn on_worktree_store_event( - &mut self, - _: Model, - evt: &WorktreeStoreEvent, - cx: &mut ModelContext, - ) { - match evt { - WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => { - self.root_points.remove(&worktree_id); - cx.emit(ProjectTreeEvent::WorktreeRemoved(*worktree_id)); - } - _ => {} - } - } -} diff --git a/crates/project/src/project_tree/path_trie.rs b/crates/project/src/project_tree/path_trie.rs deleted file mode 100644 index b2e2d82249..0000000000 --- a/crates/project/src/project_tree/path_trie.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::{ - collections::{btree_map::Entry, BTreeMap}, - ffi::OsStr, - ops::ControlFlow, - path::{Path, PathBuf}, - sync::Arc, -}; - -/// [RootPathTrie] is a workhorse of [super::ProjectTree]. It is responsible for determining the closest known project root for a given path. -/// It also determines how much of a given path is unexplored, thus letting callers fill in that gap if needed. -/// Conceptually, it allows one to annotate Worktree entries with arbitrary extra metadata and run closest-ancestor searches. -/// -/// A path is unexplored when the closest ancestor of a path is not the path itself; that means that we have not yet ran the scan on that path. -/// For example, if there's a project root at path `python/project` and we query for a path `python/project/subdir/another_subdir/file.py`, there is -/// a known root at `python/project` and the unexplored part is `subdir/another_subdir` - we need to run a scan on these 2 directories. -pub(super) struct RootPathTrie