diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index d288f0021fc..4561ace3b3d 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -3594,8 +3594,10 @@ impl LocalLspStore { } } servers_to_remove.retain(|server_id| !servers_to_preserve.contains(server_id)); - self.language_server_ids - .retain(|_, state| !servers_to_remove.contains(&state.id)); + self.language_server_ids.retain(|seed, state| { + seed.worktree_id != id_to_remove && !servers_to_remove.contains(&state.id) + }); + self.lsp_tree.instances.remove(&id_to_remove); for server_id_to_remove in &servers_to_remove { self.language_server_watched_paths .remove(server_id_to_remove); @@ -10101,6 +10103,16 @@ impl LspStore { .map(|(key, value)| (*key, value)) } + #[cfg(feature = "test-support")] + pub fn has_language_server_seed_for_worktree(&self, worktree_id: WorktreeId) -> bool { + self.as_local().is_some_and(|local| { + local + .language_server_ids + .keys() + .any(|seed| seed.worktree_id == worktree_id) + }) + } + pub(super) fn did_rename_entry( &self, worktree_id: WorktreeId, diff --git a/crates/project/tests/integration/lsp_store.rs b/crates/project/tests/integration/lsp_store.rs index 7d266ff1365..100042f5d99 100644 --- a/crates/project/tests/integration/lsp_store.rs +++ b/crates/project/tests/integration/lsp_store.rs @@ -1,8 +1,94 @@ use std::path::Path; -use language::{CodeLabel, HighlightId}; +use fs::FakeFs; +use futures::StreamExt; +use gpui::TestAppContext; +use language::{CodeLabel, FakeLspAdapter, HighlightId, rust_lang}; +use lsp::Uri; +use project::{Project, lsp_store::*}; +use serde_json::json; +use util::path; -use project::lsp_store::*; +use crate::init_test; + +#[gpui::test] +async fn test_removing_invisible_worktree_cleans_reused_lsp_bookkeeping(cx: &mut TestAppContext) { + init_test(cx); + cx.executor().allow_parking(); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree(path!("/the-root"), json!({ "main.rs": "fn main() {}" })) + .await; + fs.insert_tree( + path!("/the-registry"), + json!({ "dep": { "src": { "dep.rs": "pub fn dep() {}" } } }), + ) + .await; + + let project = Project::test(fs, [path!("/the-root").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 (_visible_buffer, _visible_handle) = project + .update(cx, |project, cx| { + project.open_local_buffer_with_lsp(path!("/the-root/main.rs"), cx) + }) + .await + .unwrap(); + fake_servers.next().await.unwrap(); + cx.run_until_parked(); + + let server_id = project.read_with(cx, |project, cx| { + project + .lsp_store() + .read(cx) + .language_server_statuses() + .next() + .unwrap() + .0 + }); + let external_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer_via_lsp( + Uri::from_file_path(path!("/the-registry/dep/src/dep.rs")).unwrap(), + server_id, + cx, + ) + }) + .await + .unwrap(); + cx.run_until_parked(); + + let invisible_worktree_id = + external_buffer.read_with(cx, |buffer, cx| buffer.file().unwrap().worktree_id(cx)); + project.read_with(cx, |project, cx| { + let worktree = project.worktree_for_id(invisible_worktree_id, cx).unwrap(); + assert!(!worktree.read(cx).is_visible()); + assert!( + project + .lsp_store() + .read(cx) + .has_language_server_seed_for_worktree(invisible_worktree_id) + ); + }); + + project.update(cx, |project, cx| { + project.remove_worktree(invisible_worktree_id, cx); + }); + cx.run_until_parked(); + + project.read_with(cx, |project, cx| { + let lsp_store = project.lsp_store(); + let lsp_store = lsp_store.read(cx); + assert!( + lsp_store + .language_server_statuses() + .any(|(status_server_id, _)| status_server_id == server_id) + ); + assert!(!lsp_store.has_language_server_seed_for_worktree(invisible_worktree_id)); + }); +} #[test] fn test_glob_literal_prefix() {