project: Fine-grained language server management (#24038)

Piotr Osiewicz , smit , Henrikh Kantuni , Caleb! , Marshall Bowers , Kirill Bulatov , Agus Zubiaga , Danilo , and Nate Butler created

Closes #ISSUE
https://github.com/zed-industries/zed/pull/23804
Release Notes:

- Improved detection of project roots for use by language servers.

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: smit <0xtimsb@gmail.com>
Co-authored-by: Henrikh Kantuni <henrikh.kantuni@gmail.com>
Co-authored-by: Caleb! <48127194+kaf-lamed-beyt@users.noreply.github.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Agus Zubiaga <agus@zed.dev>
Co-authored-by: Danilo <danilo@zed.dev>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>

Change summary

Cargo.toml                                        |   1 
crates/collab/src/tests/integration_tests.rs      |   5 
crates/component/Cargo.toml                       |   2 
crates/copilot/src/copilot.rs                     |   6 
crates/editor/src/editor.rs                       | 102 +-
crates/editor/src/lsp_ext.rs                      |  23 
crates/editor/src/proposed_changes_editor.rs      |   2 
crates/extension_host/src/extension_store_test.rs |   5 
crates/file_finder/src/file_finder_tests.rs       |  20 
crates/go_to_line/src/go_to_line.rs               |  33 
crates/language/src/language.rs                   |  50 +
crates/language/src/language_registry.rs          |  41 
crates/language_tools/src/lsp_log.rs              |  13 
crates/languages/src/rust.rs                      |  17 
crates/lsp/src/lsp.rs                             | 176 +++-
crates/outline/src/outline.rs                     |   9 
crates/outline_panel/src/outline_panel.rs         |  24 
crates/prettier/src/prettier.rs                   |  10 
crates/project/src/lsp_command.rs                 |   8 
crates/project/src/lsp_store.rs                   | 681 +++++++++-------
crates/project/src/prettier_store.rs              |   2 
crates/project/src/project.rs                     |  81 +
crates/project/src/project_tests.rs               |  19 
crates/project/src/project_tree.rs                | 243 ++++++
crates/project/src/project_tree/path_trie.rs      | 240 +++++
crates/project/src/project_tree/server_tree.rs    | 440 ++++++++++
crates/project_panel/src/project_panel.rs         |   6 
crates/proto/proto/zed.proto                      |   1 
crates/recent_projects/src/recent_projects.rs     |   2 
crates/tab_switcher/src/tab_switcher_tests.rs     |   4 
crates/tasks_ui/src/modal.rs                      |   2 
crates/zed/src/zed.rs                             |   4 
crates/zed/src/zed/quick_action_bar.rs            |  22 
33 files changed, 1,773 insertions(+), 521 deletions(-)

Detailed changes

Cargo.toml ๐Ÿ”—

@@ -442,6 +442,7 @@ 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"

crates/collab/src/tests/integration_tests.rs ๐Ÿ”—

@@ -4438,15 +4438,14 @@ async fn test_formatting_buffer(
         .await
         .unwrap();
     let project_b = client_b.join_remote_project(project_id, cx_b).await;
-    let lsp_store_b = project_b.update(cx_b, |p, _| p.lsp_store());
 
     let buffer_b = project_b
         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
         .await
         .unwrap();
 
-    let _handle = lsp_store_b.update(cx_b, |lsp_store, cx| {
-        lsp_store.register_buffer_with_language_servers(&buffer_b, cx)
+    let _handle = project_b.update(cx_b, |project, cx| {
+        project.register_buffer_with_language_servers(&buffer_b, cx)
     });
     let fake_language_server = fake_language_servers.next().await.unwrap();
     fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {

crates/component/Cargo.toml ๐Ÿ”—

@@ -15,7 +15,7 @@ path = "src/component.rs"
 collections.workspace = true
 gpui.workspace = true
 linkme.workspace = true
-once_cell = "1.20.3"
+once_cell.workspace = true
 parking_lot.workspace = true
 theme.workspace = true
 

crates/copilot/src/copilot.rs ๐Ÿ”—

@@ -458,12 +458,14 @@ impl Copilot {
                 .on_notification::<StatusNotification, _>(|_, _| { /* Silence the notification */ })
                 .detach();
 
-            let initialize_params = None;
             let configuration = lsp::DidChangeConfigurationParams {
                 settings: Default::default(),
             };
             let server = cx
-                .update(|cx| server.initialize(initialize_params, configuration.into(), cx))?
+                .update(|cx| {
+                    let params = server.default_initialize_params(cx);
+                    server.initialize(params, configuration.into(), cx)
+                })?
                 .await?;
 
             let status = server

crates/editor/src/editor.rs ๐Ÿ”—

@@ -134,7 +134,7 @@ use project::{
     lsp_store::{FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
     project_settings::{GitGutterSetting, ProjectSettings},
     CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
-    LspStore, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction, TaskSourceKind,
+    PrepareRenameResponse, Project, ProjectItem, ProjectTransaction, TaskSourceKind,
 };
 use rand::prelude::*;
 use rpc::{proto::*, ErrorExt};
@@ -1492,9 +1492,8 @@ impl Editor {
 
             if let Some(buffer) = buffer.read(cx).as_singleton() {
                 if let Some(project) = this.project.as_ref() {
-                    let lsp_store = project.read(cx).lsp_store();
-                    let handle = lsp_store.update(cx, |lsp_store, cx| {
-                        lsp_store.register_buffer_with_language_servers(&buffer, cx)
+                    let handle = project.update(cx, |project, cx| {
+                        project.register_buffer_with_language_servers(&buffer, cx)
                     });
                     this.registered_buffers
                         .insert(buffer.read(cx).remote_id(), handle);
@@ -1891,16 +1890,14 @@ impl Editor {
 
     fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
         let buffers = self.buffer.read(cx).all_buffers();
-        let Some(lsp_store) = self.lsp_store(cx) else {
+        let Some(project) = self.project.as_ref() else {
             return;
         };
-        lsp_store.update(cx, |lsp_store, cx| {
+        project.update(cx, |project, cx| {
             for buffer in buffers {
                 self.registered_buffers
                     .entry(buffer.read(cx).remote_id())
-                    .or_insert_with(|| {
-                        lsp_store.register_buffer_with_language_servers(&buffer, cx)
-                    });
+                    .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
             }
         })
     }
@@ -2100,14 +2097,14 @@ impl Editor {
             };
             if let Some(buffer_id) = new_cursor_position.buffer_id {
                 if !self.registered_buffers.contains_key(&buffer_id) {
-                    if let Some(lsp_store) = self.lsp_store(cx) {
-                        lsp_store.update(cx, |lsp_store, cx| {
+                    if let Some(project) = self.project.as_ref() {
+                        project.update(cx, |project, cx| {
                             let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
                                 return;
                             };
                             self.registered_buffers.insert(
                                 buffer_id,
-                                lsp_store.register_buffer_with_language_servers(&buffer, cx),
+                                project.register_buffer_with_language_servers(&buffer, cx),
                             );
                         })
                     }
@@ -11638,7 +11635,10 @@ impl Editor {
         if let Some(project) = self.project.clone() {
             self.buffer.update(cx, |multi_buffer, cx| {
                 project.update(cx, |project, cx| {
-                    project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx);
+                    project.restart_language_servers_for_buffers(
+                        multi_buffer.all_buffers().into_iter().collect(),
+                        cx,
+                    );
                 });
             })
         }
@@ -14051,12 +14051,6 @@ impl Editor {
         cx.notify();
     }
 
-    pub fn lsp_store(&self, cx: &App) -> Option<Entity<LspStore>> {
-        self.project
-            .as_ref()
-            .map(|project| project.read(cx).lsp_store())
-    }
-
     fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
         cx.notify();
     }
@@ -14083,11 +14077,11 @@ impl Editor {
                 if let Some(buffer) = buffer_edited {
                     let buffer_id = buffer.read(cx).remote_id();
                     if !self.registered_buffers.contains_key(&buffer_id) {
-                        if let Some(lsp_store) = self.lsp_store(cx) {
-                            lsp_store.update(cx, |lsp_store, cx| {
+                        if let Some(project) = self.project.as_ref() {
+                            project.update(cx, |project, cx| {
                                 self.registered_buffers.insert(
                                     buffer_id,
-                                    lsp_store.register_buffer_with_language_servers(&buffer, cx),
+                                    project.register_buffer_with_language_servers(&buffer, cx),
                                 );
                             })
                         }
@@ -14097,28 +14091,23 @@ 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
-                            .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::<HashSet<_>>();
+                        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.has_language_servers_for(buffer, cx)
+                                        });
+                                        should_discard.not().then_some(language.clone())
+                                    })
+                                })
+                                .collect::<HashSet<_>>()
+                        });
                         if !languages_affected.is_empty() {
                             self.refresh_inlay_hints(
                                 InlayHintRefreshReason::BufferEdited(languages_affected),
@@ -14711,15 +14700,18 @@ impl Editor {
         self.handle_input(text, window, cx);
     }
 
-    pub fn supports_inlay_hints(&self, cx: &App) -> bool {
+    pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
         let Some(provider) = self.semantics_provider.as_ref() else {
             return false;
         };
 
         let mut supports = false;
-        self.buffer().read(cx).for_each_buffer(|buffer| {
-            supports |= provider.supports_inlay_hints(buffer, cx);
+        self.buffer().update(cx, |this, cx| {
+            this.for_each_buffer(|buffer| {
+                supports |= provider.supports_inlay_hints(buffer, cx);
+            });
         });
+
         supports
     }
 
@@ -15267,7 +15259,7 @@ pub trait SemanticsProvider {
         cx: &mut App,
     ) -> Option<Task<anyhow::Result<InlayHint>>>;
 
-    fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &App) -> bool;
+    fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
 
     fn document_highlights(
         &self,
@@ -15661,17 +15653,13 @@ impl SemanticsProvider for Entity<Project> {
         }))
     }
 
-    fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &App) -> bool {
+    fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
         // TODO: make this work for remote projects
-        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,
-                },
-            )
+        self.update(cx, |this, cx| {
+            buffer.update(cx, |buffer, cx| {
+                this.any_language_server_supports_inlay_hints(buffer, cx)
+            })
+        })
     }
 
     fn inlay_hints(

crates/editor/src/lsp_ext.rs ๐Ÿ”—

@@ -21,7 +21,6 @@ where
     let Some(project) = &editor.project else {
         return None;
     };
-    let multibuffer = editor.buffer().read(cx);
     let mut language_servers_for = HashMap::default();
     editor
         .selections
@@ -29,29 +28,21 @@ where
         .iter()
         .filter(|selection| selection.start == selection.end)
         .filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
-        .filter_map(|(buffer_id, trigger_anchor)| {
-            let buffer = multibuffer.buffer(buffer_id)?;
+        .find_map(|(buffer_id, trigger_anchor)| {
+            let buffer = editor.buffer().read(cx).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 = 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
-                            }
-                        });
+                    let language_server_id = buffer.update(cx, |buffer, cx| {
+                        project.update(cx, |project, cx| {
+                            project.language_server_id_for_name(buffer, language_server_name, cx)
+                        })
+                    });
                     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;

crates/editor/src/proposed_changes_editor.rs ๐Ÿ”—

@@ -467,7 +467,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
         self.0.resolve_inlay_hint(hint, buffer, server_id, cx)
     }
 
-    fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &App) -> bool {
+    fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
         if let Some(buffer) = self.to_base(&buffer, &[], cx) {
             self.0.supports_inlay_hints(&buffer, cx)
         } else {

crates/extension_host/src/extension_store_test.rs ๐Ÿ”—

@@ -731,8 +731,9 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
 
     // Start a new instance of the language server.
     project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer.clone()], cx)
+        project.restart_language_servers_for_buffers(vec![buffer.clone()], cx)
     });
+    cx.executor().run_until_parked();
 
     // The extension has cached the binary path, and does not attempt
     // to reinstall it.
@@ -752,7 +753,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
 
     cx.executor().run_until_parked();
     project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer.clone()], cx)
+        project.restart_language_servers_for_buffers(vec![buffer.clone()], cx)
     });
 
     // The extension re-fetches the latest version of the language server.

crates/file_finder/src/file_finder_tests.rs ๐Ÿ”—

@@ -23,7 +23,7 @@ async fn test_matching_paths(cx: &mut TestAppContext) {
         .fs
         .as_fake()
         .insert_tree(
-            "/root",
+            path!("/root"),
             json!({
                 "a": {
                     "banana": "",
@@ -33,7 +33,7 @@ async fn test_matching_paths(cx: &mut TestAppContext) {
         )
         .await;
 
-    let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
+    let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
 
     let (picker, workspace, cx) = build_find_picker(project, cx);
 
@@ -153,7 +153,7 @@ async fn test_complex_path(cx: &mut TestAppContext) {
         .fs
         .as_fake()
         .insert_tree(
-            "/root",
+            path!("/root"),
             json!({
                 "ๅ…ถไป–": {
                     "Sๆ•ฐๆฎ่กจๆ ผ": {
@@ -164,7 +164,7 @@ async fn test_complex_path(cx: &mut TestAppContext) {
         )
         .await;
 
-    let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
+    let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
 
     let (picker, workspace, cx) = build_find_picker(project, cx);
 
@@ -194,7 +194,7 @@ async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
         .fs
         .as_fake()
         .insert_tree(
-            "/src",
+            path!("/src"),
             json!({
                 "test": {
                     first_file_name: first_file_contents,
@@ -204,7 +204,7 @@ async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
         )
         .await;
 
-    let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+    let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
 
     let (picker, workspace, cx) = build_find_picker(project, cx);
 
@@ -269,7 +269,7 @@ async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
         .fs
         .as_fake()
         .insert_tree(
-            "/src",
+            path!("/src"),
             json!({
                 "test": {
                     first_file_name: first_file_contents,
@@ -279,7 +279,7 @@ async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
         )
         .await;
 
-    let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+    let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
 
     let (picker, workspace, cx) = build_find_picker(project, cx);
 
@@ -1777,7 +1777,7 @@ async fn test_opens_file_on_modifier_keys_release(cx: &mut gpui::TestAppContext)
         .fs
         .as_fake()
         .insert_tree(
-            "/test",
+            path!("/test"),
             json!({
                 "1.txt": "// One",
                 "2.txt": "// Two",
@@ -1785,7 +1785,7 @@ async fn test_opens_file_on_modifier_keys_release(cx: &mut gpui::TestAppContext)
         )
         .await;
 
-    let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
+    let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
     let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 
     open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;

crates/go_to_line/src/go_to_line.rs ๐Ÿ”—

@@ -298,6 +298,7 @@ mod tests {
     use project::{FakeFs, Project};
     use serde_json::json;
     use std::{num::NonZeroU32, sync::Arc, time::Duration};
+    use util::path;
     use workspace::{AppState, Workspace};
 
     #[gpui::test]
@@ -305,7 +306,7 @@ mod tests {
         init_test(cx);
         let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
-            "/dir",
+            path!("/dir"),
             json!({
                 "a.rs": indoc!{"
                     struct SingleLine; // display line 0
@@ -326,7 +327,7 @@ mod tests {
         )
         .await;
 
-        let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+        let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
         let (workspace, cx) =
             cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
         let worktree_id = workspace.update(cx, |workspace, cx| {
@@ -335,7 +336,9 @@ mod tests {
             })
         });
         let _buffer = project
-            .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
+            .update(cx, |project, cx| {
+                project.open_local_buffer(path!("/dir/a.rs"), cx)
+            })
             .await
             .unwrap();
         let editor = workspace
@@ -414,14 +417,14 @@ mod tests {
 
         let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
-            "/dir",
+            path!("/dir"),
             json!({
                 "a.rs": "ฤ“lo"
             }),
         )
         .await;
 
-        let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+        let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
         let (workspace, cx) =
             cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
         workspace.update_in(cx, |workspace, window, cx| {
@@ -437,7 +440,9 @@ mod tests {
             })
         });
         let _buffer = project
-            .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
+            .update(cx, |project, cx| {
+                project.open_local_buffer(path!("/dir/a.rs"), cx)
+            })
             .await
             .unwrap();
         let editor = workspace
@@ -497,14 +502,14 @@ mod tests {
         let text = "ฤ“loไฝ ๅฅฝ";
         let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
-            "/dir",
+            path!("/dir"),
             json!({
                 "a.rs": text
             }),
         )
         .await;
 
-        let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+        let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
         let (workspace, cx) =
             cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
         workspace.update_in(cx, |workspace, window, cx| {
@@ -520,7 +525,9 @@ mod tests {
             })
         });
         let _buffer = project
-            .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
+            .update(cx, |project, cx| {
+                project.open_local_buffer(path!("/dir/a.rs"), cx)
+            })
             .await
             .unwrap();
         let editor = workspace
@@ -573,14 +580,14 @@ mod tests {
         let text = "ฤ“loไฝ ๅฅฝ";
         let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
-            "/dir",
+            path!("/dir"),
             json!({
                 "a.rs": text
             }),
         )
         .await;
 
-        let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+        let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
         let (workspace, cx) =
             cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
         workspace.update_in(cx, |workspace, window, cx| {
@@ -596,7 +603,9 @@ mod tests {
             })
         });
         let _buffer = project
-            .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
+            .update(cx, |project, cx| {
+                project.open_local_buffer(path!("/dir/a.rs"), cx)
+            })
             .await
             .unwrap();
         let editor = workspace

crates/language/src/language.rs ๐Ÿ”—

@@ -44,7 +44,6 @@ 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,6 +59,7 @@ use std::{
         Arc, LazyLock,
     },
 };
+use std::{num::NonZeroU32, sync::OnceLock};
 use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
 use task::RunnableTag;
 pub use task_context::{ContextProvider, RunnableRange};
@@ -162,6 +162,7 @@ pub struct CachedLspAdapter {
     pub adapter: Arc<dyn LspAdapter>,
     pub reinstall_attempt_count: AtomicU64,
     cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
+    attach_kind: OnceLock<Attach>,
 }
 
 impl Debug for CachedLspAdapter {
@@ -197,6 +198,7 @@ impl CachedLspAdapter {
             adapter,
             cached_binary: Default::default(),
             reinstall_attempt_count: AtomicU64::new(0),
+            attach_kind: Default::default(),
         })
     }
 
@@ -258,6 +260,38 @@ impl CachedLspAdapter {
             .cloned()
             .unwrap_or_else(|| language_name.lsp_id())
     }
+    pub fn find_project_root(
+        &self,
+        path: &Path,
+        ancestor_depth: usize,
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> Option<Arc<Path>> {
+        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<Path>),
+    ) -> (WorktreeId, Arc<Path>) {
+        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
@@ -268,6 +302,7 @@ pub trait LspAdapterDelegate: Send + Sync {
     fn http_client(&self) -> Arc<dyn HttpClient>;
     fn worktree_id(&self) -> WorktreeId;
     fn worktree_root_path(&self) -> &Path;
+    fn exists(&self, path: &Path, is_dir: Option<bool>) -> bool;
     fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus);
     async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;
 
@@ -506,6 +541,19 @@ pub trait LspAdapter: 'static + Send + Sync {
     fn prepare_initialize_params(&self, original: InitializeParams) -> Result<InitializeParams> {
         Ok(original)
     }
+    fn attach_kind(&self) -> Attach {
+        Attach::Shared
+    }
+    fn find_project_root(
+        &self,
+
+        _path: &Path,
+        _ancestor_depth: usize,
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> Option<Arc<Path>> {
+        // By default all language servers are rooted at the root of the worktree.
+        Some(Arc::from("".as_ref()))
+    }
 }
 
 async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>(

crates/language/src/language_registry.rs ๐Ÿ”—

@@ -108,6 +108,7 @@ struct LanguageRegistryState {
     available_languages: Vec<AvailableLanguage>,
     grammars: HashMap<Arc<str>, AvailableGrammar>,
     lsp_adapters: HashMap<LanguageName, Vec<Arc<CachedLspAdapter>>>,
+    all_lsp_adapters: HashMap<LanguageServerName, Arc<CachedLspAdapter>>,
     available_lsp_adapters:
         HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>,
     loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
@@ -234,6 +235,7 @@ 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(),
@@ -356,12 +358,16 @@ impl LanguageRegistry {
         adapter: Arc<dyn LspAdapter>,
     ) -> Arc<CachedLspAdapter> {
         let cached = CachedLspAdapter::new(adapter);
-        self.state
-            .write()
+        let mut state = self.state.write();
+        state
             .lsp_adapters
             .entry(language_name)
             .or_default()
             .push(cached.clone());
+        state
+            .all_lsp_adapters
+            .insert(cached.name.clone(), cached.clone());
+
         cached
     }
 
@@ -401,12 +407,17 @@ impl LanguageRegistry {
         let adapter_name = LanguageServerName(adapter.name.into());
         let capabilities = adapter.capabilities.clone();
         let initializer = adapter.initializer.take();
-        self.state
-            .write()
-            .lsp_adapters
-            .entry(language_name.clone())
-            .or_default()
-            .push(CachedLspAdapter::new(Arc::new(adapter)));
+        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.register_fake_language_server(adapter_name, capabilities, initializer)
     }
 
@@ -419,12 +430,16 @@ impl LanguageRegistry {
         adapter: crate::FakeLspAdapter,
     ) {
         let language_name = language_name.into();
-        self.state
-            .write()
+        let mut state = self.state.write();
+        let cached_adapter = CachedLspAdapter::new(Arc::new(adapter));
+        state
             .lsp_adapters
             .entry(language_name.clone())
             .or_default()
-            .push(CachedLspAdapter::new(Arc::new(adapter)));
+            .push(cached_adapter.clone());
+        state
+            .all_lsp_adapters
+            .insert(cached_adapter.name(), cached_adapter);
     }
 
     /// Register a fake language server (without the adapter)
@@ -895,6 +910,10 @@ impl LanguageRegistry {
             .unwrap_or_default()
     }
 
+    pub fn adapter_for_name(&self, name: &LanguageServerName) -> Option<Arc<CachedLspAdapter>> {
+        self.state.read().all_lsp_adapters.get(name).cloned()
+    }
+
     pub fn update_lsp_status(
         &self,
         server_name: LanguageServerName,

crates/language_tools/src/lsp_log.rs ๐Ÿ”—

@@ -735,7 +735,8 @@ impl LspLogView {
 
 * Binary: {BINARY:#?}
 
-* Running in project: {PATH:?}
+* Registered workspace folders:
+{WORKSPACE_FOLDERS}
 
 * Capabilities: {CAPABILITIES}
 
@@ -743,7 +744,15 @@ impl LspLogView {
                 NAME = server.name(),
                 ID = server.server_id(),
                 BINARY = server.binary(),
-                PATH = server.root_path(),
+                WORKSPACE_FOLDERS = server
+                    .workspace_folders()
+                    .iter()
+                    .filter_map(|path| path
+                        .to_file_path()
+                        .ok()
+                        .map(|path| path.to_string_lossy().into_owned()))
+                    .collect::<Vec<_>>()
+                    .join(", "),
                 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())

crates/languages/src/rust.rs ๐Ÿ”—

@@ -74,6 +74,23 @@ impl LspAdapter for RustLspAdapter {
         Self::SERVER_NAME.clone()
     }
 
+    fn find_project_root(
+        &self,
+        path: &Path,
+        ancestor_depth: usize,
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> Option<Arc<Path>> {
+        let mut outermost_cargo_toml = None;
+        for path in path.ancestors().take(ancestor_depth) {
+            let p = path.join("Cargo.toml");
+            if delegate.exists(&p, Some(false)) {
+                outermost_cargo_toml = Some(Arc::from(path));
+            }
+        }
+
+        outermost_cargo_toml
+    }
+
     async fn check_if_user_installed(
         &self,
         delegate: &dyn LspAdapterDelegate,

crates/lsp/src/lsp.rs ๐Ÿ”—

@@ -7,6 +7,7 @@ use anyhow::{anyhow, Context as _, Result};
 use collections::HashMap;
 use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, Future, FutureExt};
 use gpui::{App, AsyncApp, BackgroundExecutor, SharedString, Task};
+use notification::DidChangeWorkspaceFolders;
 use parking_lot::{Mutex, RwLock};
 use postage::{barrier, prelude::Stream};
 use schemars::{
@@ -23,10 +24,11 @@ use smol::{
 };
 
 use std::{
+    collections::BTreeSet,
     ffi::{OsStr, OsString},
     fmt,
     io::Write,
-    ops::DerefMut,
+    ops::{Deref, DerefMut},
     path::PathBuf,
     pin::Pin,
     sync::{
@@ -96,9 +98,8 @@ pub struct LanguageServer {
     #[allow(clippy::type_complexity)]
     io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
     output_done_rx: Mutex<Option<barrier::Receiver>>,
-    root_path: PathBuf,
-    working_dir: PathBuf,
     server: Arc<Mutex<Option<Child>>>,
+    workspace_folders: Arc<Mutex<BTreeSet<Url>>>,
 }
 
 /// Identifies a running language server.
@@ -376,8 +377,6 @@ impl LanguageServer {
             Some(stderr),
             stderr_capture,
             Some(server),
-            root_path,
-            working_dir,
             code_action_kinds,
             binary,
             cx,
@@ -403,8 +402,6 @@ impl LanguageServer {
         stderr: Option<Stderr>,
         stderr_capture: Arc<Mutex<Option<String>>>,
         server: Option<Child>,
-        root_path: &Path,
-        working_dir: &Path,
         code_action_kinds: Option<Vec<CodeActionKind>>,
         binary: LanguageServerBinary,
         cx: AsyncApp,
@@ -488,9 +485,8 @@ 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(),
         }
     }
 
@@ -615,12 +611,11 @@ impl LanguageServer {
     }
 
     pub fn default_initialize_params(&self, cx: &App) -> InitializeParams {
-        let root_uri = Url::from_file_path(&self.working_dir).unwrap();
         #[allow(deprecated)]
         InitializeParams {
             process_id: None,
             root_path: None,
-            root_uri: Some(root_uri.clone()),
+            root_uri: None,
             initialization_options: None,
             capabilities: ClientCapabilities {
                 general: Some(GeneralClientCapabilities {
@@ -790,10 +785,7 @@ impl LanguageServer {
                 }),
             },
             trace: None,
-            workspace_folders: Some(vec![WorkspaceFolder {
-                uri: root_uri,
-                name: Default::default(),
-            }]),
+            workspace_folders: None,
             client_info: release_channel::ReleaseChannel::try_global(cx).map(|release_channel| {
                 ClientInfo {
                     name: release_channel.display_name().to_string(),
@@ -812,16 +804,10 @@ impl LanguageServer {
     /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize)
     pub fn initialize(
         mut self,
-        initialize_params: Option<InitializeParams>,
+        params: InitializeParams,
         configuration: Arc<DidChangeConfigurationParams>,
         cx: &App,
     ) -> Task<Result<Arc<Self>>> {
-        let params = if let Some(params) = initialize_params {
-            params
-        } else {
-            self.default_initialize_params(cx)
-        };
-
         cx.spawn(|_| async move {
             let response = self.request::<request::Initialize>(params).await?;
             if let Some(info) = response.server_info {
@@ -1073,16 +1059,10 @@ 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)
@@ -1210,6 +1190,118 @@ 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::<DidChangeWorkspaceFolders>(&params).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::<DidChangeWorkspaceFolders>(&params).log_err();
+        }
+    }
+    pub fn set_workspace_folders(&self, folders: BTreeSet<Url>) {
+        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::<DidChangeWorkspaceFolders>(&params).log_err();
+        }
+    }
+
+    pub fn workspace_folders(&self) -> impl Deref<Target = BTreeSet<Url>> + '_ {
+        self.workspace_folders.lock()
+    }
+
+    pub fn register_buffer(
+        &self,
+        uri: Url,
+        language_id: String,
+        version: i32,
+        initial_text: String,
+    ) {
+        self.notify::<notification::DidOpenTextDocument>(&DidOpenTextDocumentParams {
+            text_document: TextDocumentItem::new(uri, language_id, version, initial_text),
+        })
+        .log_err();
+    }
+
+    pub fn unregister_buffer(&self, uri: Url) {
+        self.notify::<notification::DidCloseTextDocument>(&DidCloseTextDocumentParams {
+            text_document: TextDocumentIdentifier::new(uri),
+        })
+        .log_err();
+    }
 }
 
 impl Drop for LanguageServer {
@@ -1291,8 +1383,6 @@ 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(
@@ -1303,8 +1393,6 @@ impl FakeLanguageServer {
             None::<async_pipe::PipeReader>,
             Arc::new(Mutex::new(None)),
             None,
-            root,
-            root,
             None,
             binary.clone(),
             cx.clone(),
@@ -1322,8 +1410,6 @@ impl FakeLanguageServer {
                     None::<async_pipe::PipeReader>,
                     Arc::new(Mutex::new(None)),
                     None,
-                    root,
-                    root,
                     None,
                     binary,
                     cx.clone(),
@@ -1360,16 +1446,6 @@ 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"))]
@@ -1557,12 +1633,14 @@ mod tests {
             })
             .detach();
 
-        let initialize_params = None;
-        let configuration = DidChangeConfigurationParams {
-            settings: Default::default(),
-        };
         let server = cx
-            .update(|cx| server.initialize(initialize_params, configuration.into(), cx))
+            .update(|cx| {
+                let params = server.default_initialize_params(cx);
+                let configuration = DidChangeConfigurationParams {
+                    settings: Default::default(),
+                };
+                server.initialize(params, configuration.into(), cx)
+            })
             .await
             .unwrap();
         server

crates/outline/src/outline.rs ๐Ÿ”—

@@ -370,6 +370,7 @@ mod tests {
     use language::{Language, LanguageConfig, LanguageMatcher};
     use project::{FakeFs, Project};
     use serde_json::json;
+    use util::path;
     use workspace::{AppState, Workspace};
 
     #[gpui::test]
@@ -377,7 +378,7 @@ mod tests {
         init_test(cx);
         let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
-            "/dir",
+            path!("/dir"),
             json!({
                 "a.rs": indoc!{"
                     struct SingleLine; // display line 0
@@ -391,7 +392,7 @@ mod tests {
         )
         .await;
 
-        let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+        let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
         project.read_with(cx, |project, _| project.languages().add(rust_lang()));
 
         let (workspace, cx) =
@@ -402,7 +403,9 @@ mod tests {
             })
         });
         let _buffer = project
-            .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
+            .update(cx, |project, cx| {
+                project.open_local_buffer(path!("/dir/a.rs"), cx)
+            })
             .await
             .unwrap();
         let editor = workspace

crates/outline_panel/src/outline_panel.rs ๐Ÿ”—

@@ -5169,6 +5169,7 @@ mod tests {
     use project::FakeFs;
     use search::project_search::{self, perform_project_search};
     use serde_json::json;
+    use util::path;
     use workspace::OpenVisible;
 
     use super::*;
@@ -5546,8 +5547,8 @@ mod tests {
         init_test(cx);
 
         let fs = FakeFs::new(cx.background_executor.clone());
-        populate_with_test_ra_project(&fs, "/rust-analyzer").await;
-        let project = Project::test(fs.clone(), ["/rust-analyzer".as_ref()], cx).await;
+        populate_with_test_ra_project(&fs, path!("/rust-analyzer")).await;
+        let project = Project::test(fs.clone(), [path!("/rust-analyzer").as_ref()], cx).await;
         project.read_with(cx, |project, _| {
             project.languages().add(Arc::new(rust_lang()))
         });
@@ -5591,15 +5592,17 @@ mod tests {
                     );
                 });
         });
-        let all_matches = r#"/rust-analyzer/
+        let root_path = format!("{}/", path!("/rust-analyzer"));
+        let all_matches = format!(
+            r#"{root_path}
   crates/
     ide/src/
       inlay_hints/
         fn_lifetime_fn.rs
-          search: match config.param_names_for_lifetime_elision_hints {
-          search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
-          search: Some(it) if config.param_names_for_lifetime_elision_hints => {
-          search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
+          search: match config.param_names_for_lifetime_elision_hints {{
+          search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {{
+          search: Some(it) if config.param_names_for_lifetime_elision_hints => {{
+          search: InlayHintsConfig {{ param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }},
       inlay_hints.rs
         search: pub param_names_for_lifetime_elision_hints: bool,
         search: param_names_for_lifetime_elision_hints: self
@@ -5610,7 +5613,8 @@ mod tests {
         analysis_stats.rs
           search: param_names_for_lifetime_elision_hints: true,
       config.rs
-        search: param_names_for_lifetime_elision_hints: self"#;
+        search: param_names_for_lifetime_elision_hints: self"#
+        );
         let select_first_in_all_matches = |line_to_select: &str| {
             assert!(all_matches.contains(line_to_select));
             all_matches.replacen(
@@ -5921,7 +5925,7 @@ mod tests {
     async fn test_navigating_in_singleton(cx: &mut TestAppContext) {
         init_test(cx);
 
-        let root = "/root";
+        let root = path!("/root");
         let fs = FakeFs::new(cx.background_executor.clone());
         fs.insert_tree(
             root,
@@ -5968,7 +5972,7 @@ struct OutlineEntryExcerpt {
 
         let _editor = workspace
             .update(cx, |workspace, window, cx| {
-                workspace.open_abs_path(PathBuf::from("/root/src/lib.rs"), true, window, cx)
+                workspace.open_abs_path(PathBuf::from(path!("/root/src/lib.rs")), true, window, cx)
             })
             .unwrap()
             .await

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| {
-                executor.spawn(server.initialize(initialize_params, configuration.into(), cx))
+                let params = server.default_initialize_params(cx);
+                let configuration = lsp::DidChangeConfigurationParams {
+                    settings: Default::default(),
+                };
+                executor.spawn(server.initialize(params, configuration.into(), cx))
             })?
             .await
             .context("prettier server initialization")?;

crates/project/src/lsp_command.rs ๐Ÿ”—

@@ -941,9 +941,11 @@ fn language_server_for_buffer(
 ) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
     lsp_store
         .update(cx, |lsp_store, cx| {
-            lsp_store
-                .language_server_for_local_buffer(buffer.read(cx), server_id, cx)
-                .map(|(adapter, server)| (adapter.clone(), server.clone()))
+            buffer.update(cx, |buffer, cx| {
+                lsp_store
+                    .language_server_for_local_buffer(buffer, server_id, cx)
+                    .map(|(adapter, server)| (adapter.clone(), server.clone()))
+            })
         })?
         .ok_or_else(|| anyhow!("no language server found for buffer"))
 }

crates/project/src/lsp_store.rs ๐Ÿ”—

@@ -6,6 +6,7 @@ use crate::{
     lsp_ext_command,
     prettier_store::{self, PrettierStore, PrettierStoreEvent},
     project_settings::{LspSettings, ProjectSettings},
+    project_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition, ProjectTree},
     relativize_path, resolve_path,
     toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
     worktree_store::{WorktreeStore, WorktreeStoreEvent},
@@ -37,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,
     CompletionDocumentation, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, File as _, Language,
-    LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, LocalFile,
-    LspAdapter, LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16,
-    Transaction, Unclipped,
+    LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, LocalFile, LspAdapter,
+    LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
+    Unclipped,
 };
 use lsp::{
     notification::DidRenameFiles, CodeActionKind, CompletionContext, DiagnosticSeverity,
@@ -47,8 +48,8 @@ use lsp::{
     FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher,
     InsertTextFormat, LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions,
     LanguageServerId, LanguageServerName, LspRequestFuture, MessageActionItem, MessageType, OneOf,
-    RenameFilesParams, ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, Url,
-    WillRenameFiles, WorkDoneProgressCancelParams, WorkspaceFolder,
+    RenameFilesParams, ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, WillRenameFiles,
+    WorkDoneProgressCancelParams, WorkspaceFolder,
 };
 use node_runtime::read_package_installed_version;
 use parking_lot::Mutex;
@@ -80,6 +81,7 @@ use std::{
     time::{Duration, Instant},
 };
 use text::{Anchor, BufferId, LineEnding, OffsetRangeExt, TransactionId};
+use url::Url;
 use util::{
     debug_panic, defer, maybe, merge_json_value_into, paths::SanitizedPath, post_inc, ResultExt,
     TryFutureExt as _,
@@ -132,13 +134,14 @@ impl FormatTrigger {
 }
 
 pub struct LocalLspStore {
+    weak: WeakEntity<LspStore>,
     worktree_store: Entity<WorktreeStore>,
     toolchain_store: Entity<ToolchainStore>,
     http_client: Arc<dyn HttpClient>,
     environment: Entity<ProjectEnvironment>,
     fs: Arc<dyn Fs>,
     languages: Arc<LanguageRegistry>,
-    language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
+    language_server_ids: HashMap<(WorktreeId, LanguageServerName), BTreeSet<LanguageServerId>>,
     yarn: Entity<YarnPathStore>,
     pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
     buffers_being_formatted: HashSet<BufferId>,
@@ -151,7 +154,6 @@ pub struct LocalLspStore {
     supplementary_language_servers:
         HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
     prettier_store: Entity<PrettierStore>,
-    current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
     next_diagnostic_group_id: usize,
     diagnostics: HashMap<
         WorktreeId,
@@ -165,6 +167,7 @@ pub struct LocalLspStore {
     >,
     buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
     _subscription: gpui::Subscription,
+    lsp_tree: Entity<LanguageServerTree>,
     registered_buffers: HashMap<BufferId, usize>,
 }
 
@@ -178,7 +181,7 @@ impl LocalLspStore {
 
         match language_server_state {
             LanguageServerState::Running { server, .. } => Some(server),
-            LanguageServerState::Starting(_) => None,
+            LanguageServerState::Starting { .. } => None,
         }
     }
 
@@ -187,26 +190,15 @@ impl LocalLspStore {
         worktree_handle: &Entity<Worktree>,
         delegate: Arc<LocalLspAdapterDelegate>,
         adapter: Arc<CachedLspAdapter>,
-        cx: &mut Context<LspStore>,
-    ) {
+        settings: Arc<LspSettings>,
+        cx: &mut App,
+    ) -> LanguageServerId {
         let worktree = worktree_handle.read(cx);
         let worktree_id = worktree.id();
         let root_path = worktree.abs_path();
         let key = (worktree_id, adapter.name.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 override_options = settings.initialization_options.clone();
 
         let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
 
@@ -222,12 +214,13 @@ 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 |_lsp_store, cx| async move {
+            move |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,
@@ -254,13 +247,16 @@ impl LocalLspStore {
             }
         });
 
-        let state = LanguageServerState::Starting({
+        let pending_workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default();
+        let startup = {
             let server_name = adapter.name.0.clone();
             let delegate = delegate as Arc<dyn LspAdapterDelegate>;
             let key = key.clone();
             let adapter = adapter.clone();
+            let this = self.weak.clone();
+            let pending_workspace_folders = pending_workspace_folders.clone();
             let fs = self.fs.clone();
-            cx.spawn(move |this, mut cx| async move {
+            cx.spawn(move |mut cx| async move {
                 let result = {
                     let delegate = delegate.clone();
                     let adapter = adapter.clone();
@@ -318,7 +314,7 @@ impl LocalLspStore {
                         let language_server = cx
                             .update(|cx| {
                                 language_server.initialize(
-                                    Some(initialization_params),
+                                    initialization_params,
                                     did_change_configuration_params.clone(),
                                     cx,
                                 )
@@ -352,6 +348,7 @@ impl LocalLspStore {
                                 server.clone(),
                                 server_id,
                                 key,
+                                pending_workspace_folders,
                                 &mut cx,
                             );
                         })
@@ -374,82 +371,18 @@ impl LocalLspStore {
                     }
                 }
             })
-        });
+        };
+        let state = LanguageServerState::Starting {
+            startup,
+            pending_workspace_folders,
+        };
 
         self.language_servers.insert(server_id, state);
-        self.language_server_ids.insert(key, server_id);
-    }
-
-    pub fn start_language_servers(
-        &mut self,
-        worktree: &Entity<Worktree>,
-        language: LanguageName,
-        cx: &mut Context<LspStore>,
-    ) {
-        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::<Vec<_>>();
-
-        let desired_language_servers =
-            settings.customized_language_servers(&available_language_servers);
-
-        let mut enabled_lsp_adapters: Vec<Arc<CachedLspAdapter>> = 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_entity(),
-                &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);
+        self.language_server_ids
+            .entry(key)
+            .or_default()
+            .insert(server_id);
+        server_id
     }
 
     fn get_language_server_binary(
@@ -457,7 +390,7 @@ impl LocalLspStore {
         adapter: Arc<CachedLspAdapter>,
         delegate: Arc<dyn LspAdapterDelegate>,
         allow_binary_download: bool,
-        cx: &mut Context<LspStore>,
+        cx: &mut App,
     ) -> Task<Result<LanguageServerBinary>> {
         let settings = ProjectSettings::get(
             Some(SettingsLocation {
@@ -472,7 +405,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),
@@ -493,7 +426,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(
@@ -596,14 +529,16 @@ impl LocalLspStore {
                         else {
                             return Ok(None);
                         };
-                        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(),
-                        }]))
+                        let root = server.workspace_folders();
+                        Ok(Some(
+                            root.iter()
+                                .cloned()
+                                .map(|uri| WorkspaceFolder {
+                                    uri,
+                                    name: Default::default(),
+                                })
+                                .collect(),
+                        ))
                     }
                 }
             })
@@ -1024,7 +959,7 @@ impl LocalLspStore {
                 use LanguageServerState::*;
                 match server_state {
                     Running { server, .. } => server.shutdown()?.await,
-                    Starting(task) => task.await?.shutdown()?.await,
+                    Starting { startup, .. } => startup.await?.shutdown()?.await,
                 }
             })
             .collect::<Vec<_>>();
@@ -1040,42 +975,74 @@ impl LocalLspStore {
     ) -> impl Iterator<Item = &Arc<LanguageServer>> {
         self.language_server_ids
             .iter()
-            .filter_map(move |((language_server_worktree_id, _), id)| {
-                if *language_server_worktree_id == worktree_id {
+            .flat_map(move |((language_server_path, _), ids)| {
+                ids.iter().filter_map(move |id| {
+                    if *language_server_path != worktree_id {
+                        return None;
+                    }
                     if let Some(LanguageServerState::Running { server, .. }) =
                         self.language_servers.get(id)
                     {
                         return Some(server);
+                    } else {
+                        None
                     }
-                }
-                None
+                })
             })
     }
 
-    pub(crate) fn language_server_ids_for_buffer(
+    fn language_server_ids_for_project_path(
+        &self,
+        project_path: ProjectPath,
+        language: &Language,
+        cx: &mut App,
+    ) -> Vec<LanguageServerId> {
+        let Some(worktree) = self
+            .worktree_store
+            .read(cx)
+            .worktree_for_id(project_path.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(
+                project_path,
+                AdapterQuery::Language(&language.name()),
+                delegate,
+                cx,
+            )
+            .filter_map(|node| node.server_id())
+            .collect::<Vec<_>>()
+        });
+
+        root
+    }
+
+    fn language_server_ids_for_buffer(
         &self,
         buffer: &Buffer,
-        cx: &App,
+        cx: &mut App,
     ) -> Vec<LanguageServerId> {
         if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
             let worktree_id = file.worktree_id(cx);
-            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()
+
+            let path: Arc<Path> = file
+                .path()
+                .parent()
+                .map(Arc::from)
+                .unwrap_or_else(|| file.path().clone());
+            let worktree_path = ProjectPath { worktree_id, path };
+            self.language_server_ids_for_project_path(worktree_path, language, cx)
         } else {
             Vec::new()
         }
     }
 
-    pub(crate) fn language_servers_for_buffer<'a>(
+    fn language_servers_for_buffer<'a>(
         &'a self,
         buffer: &'a Buffer,
-        cx: &'a App,
+        cx: &'a mut App,
     ) -> impl Iterator<Item = (&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
         self.language_server_ids_for_buffer(buffer, cx)
             .into_iter()
@@ -1090,7 +1057,7 @@ impl LocalLspStore {
     fn primary_language_server_for_buffer<'a>(
         &'a self,
         buffer: &'a Buffer,
-        cx: &'a App,
+        cx: &'a mut App,
     ) -> Option<(&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
         // 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
@@ -1134,14 +1101,14 @@ impl LocalLspStore {
         let mut project_transaction = ProjectTransaction::default();
         for buffer in &buffers {
             let adapters_and_servers = lsp_store.update(&mut cx, |lsp_store, cx| {
-                let buffer = buffer.handle.read(cx);
-
-                lsp_store
-                    .as_local()
-                    .unwrap()
-                    .language_servers_for_buffer(buffer, cx)
-                    .map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
-                    .collect::<Vec<_>>()
+                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::<Vec<_>>()
+                })
             })?;
 
             let settings = buffer.handle.update(&mut cx, |buffer, cx| {
@@ -1252,11 +1219,13 @@ impl LocalLspStore {
             let operation = match formatter {
                 Formatter::LanguageServer { name } => {
                     let Some(language_server) = lsp_store.update(cx, |lsp_store, cx| {
-                        lsp_store
-                            .as_local()
-                            .unwrap()
-                            .primary_language_server_for_buffer(buffer.handle.read(cx), cx)
-                            .map(|(_, lsp)| lsp.clone())
+                        buffer.handle.update(cx, |buffer, cx| {
+                            lsp_store
+                                .as_local()
+                                .unwrap()
+                                .primary_language_server_for_buffer(buffer, cx)
+                                .map(|(_, lsp)| lsp.clone())
+                        })
                     })?
                     else {
                         continue;
@@ -1630,7 +1599,8 @@ impl LocalLspStore {
     fn initialize_buffer(&mut self, buffer_handle: &Entity<Buffer>, cx: &mut Context<LspStore>) {
         let buffer = buffer_handle.read(cx);
 
-        let Some(file) = File::from_dyn(buffer.file()) else {
+        let file = buffer.file().cloned();
+        let Some(file) = File::from_dyn(file.as_ref()) else {
             return;
         };
         if !file.is_local() {
@@ -1648,60 +1618,64 @@ impl LocalLspStore {
                     .log_err();
             }
         }
-
         let Some(language) = language else {
             return;
         };
         for adapter in self.languages.lsp_adapters(&language.name()) {
-            let server = self
+            let servers = 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,
-            };
+                .get(&(worktree_id, adapter.name.clone()));
+            if let Some(server_ids) = servers {
+                for server_id in server_ids {
+                    let server = self
+                        .language_servers
+                        .get(server_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
+                    buffer_handle.update(cx, |buffer, cx| {
+                        buffer.set_completion_triggers(
+                            server.server_id(),
+                            server
+                                .capabilities()
+                                .completion_provider
                                 .as_ref()
-                                .map(|characters| characters.iter().cloned().collect())
-                        })
-                        .unwrap_or_default(),
-                    cx,
-                );
-            });
+                                .and_then(|provider| {
+                                    provider
+                                        .trigger_characters
+                                        .as_ref()
+                                        .map(|characters| characters.iter().cloned().collect())
+                                })
+                                .unwrap_or_default(),
+                            cx,
+                        );
+                    });
+                }
+            }
         }
     }
 
     pub(crate) fn reset_buffer(&mut self, buffer: &Entity<Buffer>, old_file: &File, cx: &mut App) {
         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() {
-                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);
-                    }
-                }
+            let Some(language) = buffer.language() else {
+                return;
+            };
+            let path = ProjectPath {
+                worktree_id: old_file.worktree_id(cx),
+                path: old_file.path.clone(),
+            };
+            for server_id in self.language_server_ids_for_project_path(path, language, cx) {
+                buffer.update_diagnostics(server_id, DiagnosticSet::new([], buffer), cx);
+                buffer.set_completion_triggers(server_id, Default::default(), cx);
             }
         });
     }
@@ -1801,86 +1775,202 @@ 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;
         };
-        self.start_language_servers(&worktree, language.name(), cx);
+        let path: Arc<Path> = file
+            .path()
+            .parent()
+            .map(Arc::from)
+            .unwrap_or_else(|| file.path().clone());
+        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 },
+                AdapterQuery::Language(&language.name()),
+                delegate.clone(),
+                cx,
+            )
+            .collect::<Vec<_>>()
+        });
+        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::<Vec<_>>();
+        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,
+                );
+            });
+        }
         for adapter in self.languages.lsp_adapters(&language.name()) {
-            let server = self
+            let servers = 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
-                    }
+                .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
+                            }
+                        })
+                    })
                 });
-            let server = match server {
+            let servers = match servers {
                 Some(server) => server,
                 None => continue,
             };
 
-            server
-                .notify::<lsp::notification::DidOpenTextDocument>(&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]);
+            for server in servers {
+                let snapshot = LspBufferSnapshot {
+                    version: 0,
+                    snapshot: initial_snapshot.clone(),
+                };
+                self.buffer_snapshots
+                    .entry(buffer_id)
+                    .or_default()
+                    .insert(server.server_id(), vec![snapshot]);
+
+                server.register_buffer(
+                    uri.clone(),
+                    adapter.language_id(&language.name()),
+                    0,
+                    initial_snapshot.text(),
+                );
+            }
         }
     }
+
     pub(crate) fn unregister_old_buffer_from_language_servers(
         &mut self,
         buffer: &Entity<Buffer>,
         old_file: &File,
-
         cx: &mut App,
     ) {
         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.as_path()).unwrap_or_else(|_| {
-            panic!(
+
+        let Ok(file_url) = lsp::Url::from_file_path(old_path.as_path()) else {
+            debug_panic!(
                 "`{}` is not parseable as an URI",
                 old_path.to_string_lossy()
-            )
-        });
-        self.unregister_buffer_from_language_servers(buffer, file_url, cx);
+            );
+            return;
+        };
+        self.unregister_buffer_from_language_servers(buffer, &file_url, cx);
     }
 
     pub(crate) fn unregister_buffer_from_language_servers(
         &mut self,
         buffer: &Entity<Buffer>,
-        file_url: lsp::Url,
+        file_url: &lsp::Url,
         cx: &mut App,
     ) {
         buffer.update(cx, |buffer, cx| {
-            self.buffer_snapshots.remove(&buffer.remote_id());
+            let _ = self.buffer_snapshots.remove(&buffer.remote_id());
+
             for (_, language_server) in self.language_servers_for_buffer(buffer, cx) {
-                language_server
-                    .notify::<lsp::notification::DidCloseTextDocument>(
-                        &lsp::DidCloseTextDocumentParams {
-                            text_document: lsp::TextDocumentIdentifier::new(file_url.clone()),
-                        },
-                    )
-                    .log_err();
+                language_server.unregister_buffer(file_url.clone());
             }
         });
     }
@@ -2406,6 +2496,46 @@ impl LocalLspStore {
             failure_reason: None,
         })
     }
+
+    fn remove_worktree(
+        &mut self,
+        id_to_remove: WorktreeId,
+        cx: &mut Context<'_, LspStore>,
+    ) -> Vec<LanguageServerId> {
+        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,
@@ -2742,6 +2872,7 @@ 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,
@@ -2821,23 +2952,6 @@ impl LspStore {
         }
     }
 
-    pub fn swap_current_lsp_settings(
-        &mut self,
-        new_settings: HashMap<LanguageServerName, LspSettings>,
-    ) -> Option<HashMap<LanguageServerName, LspSettings>> {
-        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: Entity<BufferStore>,
@@ -2869,8 +2983,10 @@ impl LspStore {
                 sender,
             )
         };
+        let project_tree = ProjectTree::new(worktree_store.clone(), cx);
         Self {
             mode: LspStoreMode::Local(LocalLspStore {
+                weak: cx.weak_entity(),
                 worktree_store: worktree_store.clone(),
                 toolchain_store: toolchain_store.clone(),
                 supplementary_language_servers: Default::default(),
@@ -2881,7 +2997,6 @@ 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,
@@ -2894,7 +3009,8 @@ impl LspStore {
                 _subscription: cx.on_app_quit(|this, cx| {
                     this.as_local_mut().unwrap().shutdown_language_servers(cx)
                 }),
-                registered_buffers: HashMap::default(),
+                lsp_tree: LanguageServerTree::new(project_tree, languages.clone(), cx),
+                registered_buffers: Default::default(),
             }),
             last_formatting_failure: None,
             downstream_client: None,
@@ -2908,7 +3024,7 @@ impl LspStore {
             active_entry: None,
 
             _maintain_workspace_config,
-            _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
+            _maintain_buffer_languages: Self::maintain_buffer_languages(languages, cx),
         }
     }
 
@@ -2969,17 +3085,6 @@ impl LspStore {
         }
     }
 
-    fn worktree_for_id(
-        &self,
-        worktree_id: WorktreeId,
-        cx: &Context<Self>,
-    ) -> Result<Entity<Worktree>> {
-        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,
         _: Entity<BufferStore>,
@@ -2992,9 +3097,10 @@ impl LspStore {
             }
             BufferStoreEvent::BufferChangedFilePath { buffer, old_file } => {
                 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() {
+                if let Some(local) = self.as_local_mut() {
+                    if let Some(old_file) = File::from_dyn(old_file.as_ref()) {
                         local.reset_buffer(buffer, old_file, cx);
+
                         if local.registered_buffers.contains_key(&buffer_id) {
                             local.unregister_old_buffer_from_language_servers(buffer, old_file, cx);
                         }
@@ -3128,15 +3234,14 @@ impl LspStore {
         Ok(())
     }
 
-    pub fn register_buffer_with_language_servers(
+    pub(crate) fn register_buffer_with_language_servers(
         &mut self,
         buffer: &Entity<Buffer>,
+        ignore_refcounts: bool,
         cx: &mut Context<Self>,
     ) -> OpenLspBufferHandle {
         let buffer_id = buffer.read(cx).remote_id();
-
         let handle = cx.new(|_| buffer.clone());
-
         if let Some(local) = self.as_local_mut() {
             let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
                 return handle;
@@ -3144,27 +3249,33 @@ impl LspStore {
             if !file.is_local() {
                 return handle;
             }
+
             let refcount = local.registered_buffers.entry(buffer_id).or_insert(0);
-            *refcount += 1;
-            if *refcount == 1 {
+            if !ignore_refcounts {
+                *refcount += 1;
+            }
+
+            if !ignore_refcounts || *refcount == 1 {
                 local.register_buffer_with_language_servers(buffer, cx);
             }
+            if !ignore_refcounts {
+                cx.observe_release(&handle, move |this, buffer, cx| {
+                    let local = this.as_local_mut().unwrap();
+                    let Some(refcount) = local.registered_buffers.get_mut(&buffer_id) else {
+                        debug_panic!("bad refcounting");
+                        return;
+                    };
 
-            cx.observe_release(&handle, move |this, buffer, cx| {
-                let local = this.as_local_mut().unwrap();
-                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);
+                    *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();
+                })
+                .detach();
+            }
         } else if let Some((upstream_client, upstream_project_id)) = self.upstream_client() {
             let buffer_id = buffer.read(cx).remote_id().to_proto();
             cx.background_executor()

crates/project/src/prettier_store.rs ๐Ÿ”—

@@ -40,7 +40,7 @@ pub struct PrettierStore {
     prettier_instances: HashMap<PathBuf, PrettierInstance>,
 }
 
-pub enum PrettierStoreEvent {
+pub(crate) enum PrettierStoreEvent {
     LanguageServerRemoved(LanguageServerId),
     LanguageServerAdded {
         new_server_id: LanguageServerId,

crates/project/src/project.rs ๐Ÿ”—

@@ -9,6 +9,7 @@ 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;
@@ -56,17 +57,16 @@ use gpui::{
 };
 use itertools::Itertools;
 use language::{
-    language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent,
-    CachedLspAdapter, Capability, CodeLabel, CompletionDocumentation, File as _, Language,
-    LanguageName, LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainList,
-    Transaction, Unclipped,
+    language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent, Capability,
+    CodeLabel, CompletionDocumentation, File as _, Language, LanguageName, LanguageRegistry,
+    PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainList, Transaction, Unclipped,
 };
 use lsp::{
-    CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer,
-    LanguageServerId, LanguageServerName, MessageActionItem,
+    CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServerId,
+    LanguageServerName, MessageActionItem,
 };
 use lsp_command::*;
-use lsp_store::LspFormatTarget;
+use lsp_store::{LspFormatTarget, OpenLspBufferHandle};
 use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 pub use prettier_store::PrettierStore;
@@ -481,6 +481,7 @@ 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,
@@ -1970,7 +1971,7 @@ impl Project {
     pub fn open_buffer(
         &mut self,
         path: impl Into<ProjectPath>,
-        cx: &mut Context<Self>,
+        cx: &mut App,
     ) -> Task<Result<Entity<Buffer>>> {
         if self.is_disconnected(cx) {
             return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
@@ -1988,16 +1989,25 @@ impl Project {
         cx: &mut Context<Self>,
     ) -> Task<Result<(Entity<Buffer>, 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(|this, 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)
+            let handle = this.update(&mut cx, |project, cx| {
+                project.register_buffer_with_language_servers(&buffer, cx)
             })?;
             Ok((buffer, handle))
         })
     }
 
+    pub fn register_buffer_with_language_servers(
+        &self,
+        buffer: &Entity<Buffer>,
+        cx: &mut App,
+    ) -> OpenLspBufferHandle {
+        self.lsp_store.update(cx, |lsp_store, cx| {
+            lsp_store.register_buffer_with_language_servers(&buffer, false, cx)
+        })
+    }
+
     pub fn open_unstaged_diff(
         &mut self,
         buffer: Entity<Buffer>,
@@ -2616,7 +2626,7 @@ impl Project {
 
     pub fn restart_language_servers_for_buffers(
         &mut self,
-        buffers: impl IntoIterator<Item = Entity<Buffer>>,
+        buffers: Vec<Entity<Buffer>>,
         cx: &mut Context<Self>,
     ) {
         self.lsp_store.update(cx, |lsp_store, cx| {
@@ -4228,14 +4238,43 @@ impl Project {
         self.lsp_store.read(cx).supplementary_language_servers()
     }
 
-    pub fn language_servers_for_local_buffer<'a>(
-        &'a self,
-        buffer: &'a Buffer,
-        cx: &'a App,
-    ) -> impl Iterator<Item = (&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
-        self.lsp_store
-            .read(cx)
-            .language_servers_for_local_buffer(buffer, cx)
+    pub fn any_language_server_supports_inlay_hints(&self, buffer: &Buffer, cx: &mut App) -> bool {
+        self.lsp_store.update(cx, |this, cx| {
+            this.language_servers_for_local_buffer(buffer, cx)
+                .any(
+                    |(_, server)| match server.capabilities().inlay_hint_provider {
+                        Some(lsp::OneOf::Left(enabled)) => enabled,
+                        Some(lsp::OneOf::Right(_)) => true,
+                        None => false,
+                    },
+                )
+        })
+    }
+
+    pub fn language_server_id_for_name(
+        &self,
+        buffer: &Buffer,
+        name: &str,
+        cx: &mut App,
+    ) -> Option<LanguageServerId> {
+        self.lsp_store.update(cx, |this, cx| {
+            this.language_servers_for_local_buffer(buffer, cx)
+                .find_map(|(adapter, server)| {
+                    if adapter.name.0 == name {
+                        Some(server.server_id())
+                    } else {
+                        None
+                    }
+                })
+        })
+    }
+
+    pub fn has_language_servers_for(&self, buffer: &Buffer, cx: &mut App) -> bool {
+        self.lsp_store.update(cx, |this, cx| {
+            this.language_servers_for_local_buffer(buffer, cx)
+                .next()
+                .is_some()
+        })
     }
 
     pub fn buffer_store(&self) -> &Entity<BufferStore> {

crates/project/src/project_tests.rs ๐Ÿ”—

@@ -1406,14 +1406,13 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
         })
         .await
         .unwrap();
-
     // Simulate diagnostics starting to update.
     let fake_server = fake_servers.next().await.unwrap();
     fake_server.start_progress(progress_token).await;
 
     // Restart the server before the diagnostics finish updating.
     project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer], cx);
+        project.restart_language_servers_for_buffers(vec![buffer], cx);
     });
     let mut events = cx.events(&project);
 
@@ -1518,7 +1517,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
     });
 
     project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer.clone()], cx);
+        project.restart_language_servers_for_buffers(vec![buffer.clone()], cx);
     });
 
     // The diagnostics are cleared.
@@ -1572,10 +1571,10 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
         diagnostics: Vec::new(),
     });
     cx.executor().run_until_parked();
-
     project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer.clone()], cx);
+        project.restart_language_servers_for_buffers(vec![buffer.clone()], cx);
     });
+
     let mut fake_server = fake_servers.next().await.unwrap();
     let notification = fake_server
         .receive_notification::<lsp::notification::DidOpenTextDocument>()
@@ -1782,7 +1781,6 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
     fs.insert_tree(path!("/dir"), json!({ "a.rs": text })).await;
 
     let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
-    let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 
     language_registry.add(rust_lang());
@@ -1801,8 +1799,8 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
         .await
         .unwrap();
 
-    let _handle = lsp_store.update(cx, |lsp_store, cx| {
-        lsp_store.register_buffer_with_language_servers(&buffer, cx)
+    let _handle = project.update(cx, |project, cx| {
+        project.register_buffer_with_language_servers(&buffer, cx)
     });
 
     let mut fake_server = fake_servers.next().await.unwrap();
@@ -2617,7 +2615,6 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
             ),
         )))
     });
-
     let mut definitions = project
         .update(cx, |project, cx| project.definition(&buffer, 22, cx))
         .await
@@ -3381,7 +3378,7 @@ async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
 
     let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
-        "/dir",
+        path!("/dir"),
         json!({
             "a": {
                 "file1": "",
@@ -3390,7 +3387,7 @@ async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
     )
     .await;
 
-    let project = Project::test(fs, [Path::new("/dir")], cx).await;
+    let project = Project::test(fs, [Path::new(path!("/dir"))], cx).await;
     let tree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
     let tree_id = tree.update(cx, |tree, _| tree.id());
 

crates/project/src/project_tree.rs ๐Ÿ”—

@@ -0,0 +1,243 @@
+//! 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::{App, AppContext, Context, Entity, EventEmitter, 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::{AdapterQuery, LanguageServerTree, LaunchDisposition};
+
+struct WorktreeRoots {
+    roots: RootPathTrie<LanguageServerName>,
+    worktree_store: Entity<WorktreeStore>,
+    _worktree_subscription: Subscription,
+}
+
+impl WorktreeRoots {
+    fn new(
+        worktree_store: Entity<WorktreeStore>,
+        worktree: Entity<Worktree>,
+        cx: &mut App,
+    ) -> Entity<Self> {
+        cx.new(|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<WorktreeId, Entity<WorktreeRoots>>,
+    worktree_store: Entity<WorktreeStore>,
+    _subscriptions: [Subscription; 2],
+}
+
+#[derive(Debug, Clone)]
+struct AdapterWrapper(Arc<CachedLspAdapter>);
+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<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.0.name.hash(state);
+    }
+}
+
+impl PartialOrd for AdapterWrapper {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        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<LanguageServerName> for AdapterWrapper {
+    fn borrow(&self) -> &LanguageServerName {
+        &self.0.name
+    }
+}
+
+#[derive(PartialEq)]
+pub(crate) enum ProjectTreeEvent {
+    WorktreeRemoved(WorktreeId),
+    Cleared,
+}
+
+impl EventEmitter<ProjectTreeEvent> for ProjectTree {}
+
+impl ProjectTree {
+    pub(crate) fn new(worktree_store: Entity<WorktreeStore>, cx: &mut App) -> Entity<Self> {
+        cx.new(|cx| Self {
+            root_points: Default::default(),
+            _subscriptions: [
+                cx.subscribe(&worktree_store, Self::on_worktree_store_event),
+                cx.observe_global::<SettingsStore>(|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<Arc<CachedLspAdapter>>,
+        delegate: Arc<dyn LspAdapterDelegate>,
+        cx: &mut App,
+    ) -> BTreeMap<AdapterWrapper, ProjectPath> {
+        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,
+        _: Entity<WorktreeStore>,
+        evt: &WorktreeStoreEvent,
+        cx: &mut Context<Self>,
+    ) {
+        match evt {
+            WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => {
+                self.root_points.remove(&worktree_id);
+                cx.emit(ProjectTreeEvent::WorktreeRemoved(*worktree_id));
+            }
+            _ => {}
+        }
+    }
+}

crates/project/src/project_tree/path_trie.rs ๐Ÿ”—

@@ -0,0 +1,240 @@
+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<Label> {
+    worktree_relative_path: Arc<Path>,
+    labels: BTreeMap<Label, LabelPresence>,
+    children: BTreeMap<Arc<OsStr>, RootPathTrie<Label>>,
+}
+
+/// Label presence is a marker that allows to optimize searches within [RootPathTrie]; node label can be:
+/// - Present; we know there's definitely a project root at this node and it is the only label of that kind on the path to the root of a worktree
+/// (none of it's ancestors or descendants can contain the same present label)
+/// - Known Absent - we know there's definitely no project root at this node and none of it's ancestors are Present (descendants can be present though!).
+/// - Forbidden - we know there's definitely no project root at this node and none of it's ancestors or descendants can be Present.
+/// The distinction is there to optimize searching; when we encounter a node with unknown status, we don't need to look at it's full path
+/// to the root of the worktree; it's sufficient to explore only the path between last node with a KnownAbsent state and the directory of a path, since we run searches
+/// from the leaf up to the root of the worktree. When any of the ancestors is forbidden, we don't need to look at the node or its ancestors.
+/// When there's a present labeled node on the path to the root, we don't need to ask the adapter to run the search at all.
+///
+/// In practical terms, it means that by storing label presence we don't need to do a project discovery on a given folder more than once
+/// (unless the node is invalidated, which can happen when FS entries are renamed/removed).
+///
+/// Storing project absence allows us to recognize which paths have already been scanned for a project root unsuccessfully. This way we don't need to run
+/// such scan more than once.
+#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Ord, Eq)]
+pub(super) enum LabelPresence {
+    KnownAbsent,
+    Present,
+}
+
+impl<Label: Ord + Clone> RootPathTrie<Label> {
+    pub(super) fn new() -> Self {
+        Self::new_with_key(Arc::from(Path::new("")))
+    }
+    fn new_with_key(worktree_relative_path: Arc<Path>) -> Self {
+        RootPathTrie {
+            worktree_relative_path,
+            labels: Default::default(),
+            children: Default::default(),
+        }
+    }
+    // Internal implementation of inner that allows one to visit descendants of insertion point for a node.
+    fn insert_inner(
+        &mut self,
+        path: &TriePath,
+        value: Label,
+        presence: LabelPresence,
+    ) -> &mut Self {
+        let mut current = self;
+
+        let mut path_so_far = PathBuf::new();
+        for key in path.0.iter() {
+            path_so_far.push(Path::new(key));
+            current = match current.children.entry(key.clone()) {
+                Entry::Vacant(vacant_entry) => vacant_entry
+                    .insert(RootPathTrie::new_with_key(Arc::from(path_so_far.as_path()))),
+                Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
+            };
+        }
+        let _previous_value = current.labels.insert(value, presence);
+        debug_assert_eq!(_previous_value, None);
+        current
+    }
+    pub(super) fn insert(&mut self, path: &TriePath, value: Label, presence: LabelPresence) {
+        self.insert_inner(path, value, presence);
+    }
+
+    pub(super) fn walk<'a>(
+        &'a self,
+        path: &TriePath,
+        callback: &mut dyn for<'b> FnMut(
+            &'b Arc<Path>,
+            &'a BTreeMap<Label, LabelPresence>,
+        ) -> ControlFlow<()>,
+    ) {
+        let mut current = self;
+        for key in path.0.iter() {
+            if !current.labels.is_empty() {
+                if (callback)(&current.worktree_relative_path, &current.labels).is_break() {
+                    return;
+                };
+            }
+            current = match current.children.get(key) {
+                Some(child) => child,
+                None => return,
+            };
+        }
+        if !current.labels.is_empty() {
+            (callback)(&current.worktree_relative_path, &current.labels);
+        }
+    }
+
+    pub(super) fn remove(&mut self, path: &TriePath) {
+        let mut current = self;
+        for path in path.0.iter().take(path.0.len().saturating_sub(1)) {
+            current = match current.children.get_mut(path) {
+                Some(child) => child,
+                None => return,
+            };
+        }
+        if let Some(final_entry_name) = path.0.last() {
+            current.children.remove(final_entry_name);
+        }
+    }
+}
+
+/// [TriePath] is a [Path] preprocessed for amortizing the cost of doing multiple lookups in distinct [RootPathTrie]s.
+#[derive(Clone)]
+pub(super) struct TriePath(Arc<[Arc<OsStr>]>);
+
+impl From<&Path> for TriePath {
+    fn from(value: &Path) -> Self {
+        TriePath(value.components().map(|c| c.as_os_str().into()).collect())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::collections::BTreeSet;
+
+    use super::*;
+
+    #[test]
+    fn test_insert_and_lookup() {
+        let mut trie = RootPathTrie::<()>::new();
+        trie.insert(
+            &TriePath::from(Path::new("a/b/c")),
+            (),
+            LabelPresence::Present,
+        );
+
+        trie.walk(&TriePath::from(Path::new("a/b/c")), &mut |path, nodes| {
+            assert_eq!(nodes.get(&()), Some(&LabelPresence::Present));
+            assert_eq!(path.as_ref(), Path::new("a/b/c"));
+            ControlFlow::Continue(())
+        });
+        // Now let's annotate a parent with "Known missing" node.
+        trie.insert(
+            &TriePath::from(Path::new("a")),
+            (),
+            LabelPresence::KnownAbsent,
+        );
+
+        // Ensure that we walk from the root to the leaf.
+        let mut visited_paths = BTreeSet::new();
+        trie.walk(&TriePath::from(Path::new("a/b/c")), &mut |path, nodes| {
+            if path.as_ref() == Path::new("a/b/c") {
+                assert_eq!(
+                    visited_paths,
+                    BTreeSet::from_iter([Arc::from(Path::new("a/"))])
+                );
+                assert_eq!(nodes.get(&()), Some(&LabelPresence::Present));
+            } else if path.as_ref() == Path::new("a/") {
+                assert!(visited_paths.is_empty());
+                assert_eq!(nodes.get(&()), Some(&LabelPresence::KnownAbsent));
+            } else {
+                panic!("Unknown path");
+            }
+            // Assert that we only ever visit a path once.
+            assert!(visited_paths.insert(path.clone()));
+            ControlFlow::Continue(())
+        });
+
+        // One can also pass a path whose prefix is in the tree, but not that path itself.
+        let mut visited_paths = BTreeSet::new();
+        trie.walk(
+            &TriePath::from(Path::new("a/b/c/d/e/f/g")),
+            &mut |path, nodes| {
+                if path.as_ref() == Path::new("a/b/c") {
+                    assert_eq!(
+                        visited_paths,
+                        BTreeSet::from_iter([Arc::from(Path::new("a/"))])
+                    );
+                    assert_eq!(nodes.get(&()), Some(&LabelPresence::Present));
+                } else if path.as_ref() == Path::new("a/") {
+                    assert!(visited_paths.is_empty());
+                    assert_eq!(nodes.get(&()), Some(&LabelPresence::KnownAbsent));
+                } else {
+                    panic!("Unknown path");
+                }
+                // Assert that we only ever visit a path once.
+                assert!(visited_paths.insert(path.clone()));
+                ControlFlow::Continue(())
+            },
+        );
+
+        // Test breaking from the tree-walk.
+        let mut visited_paths = BTreeSet::new();
+        trie.walk(&TriePath::from(Path::new("a/b/c")), &mut |path, nodes| {
+            if path.as_ref() == Path::new("a/") {
+                assert!(visited_paths.is_empty());
+                assert_eq!(nodes.get(&()), Some(&LabelPresence::KnownAbsent));
+            } else {
+                panic!("Unknown path");
+            }
+            // Assert that we only ever visit a path once.
+            assert!(visited_paths.insert(path.clone()));
+            ControlFlow::Break(())
+        });
+        assert_eq!(visited_paths.len(), 1);
+
+        // Entry removal.
+        trie.insert(
+            &TriePath::from(Path::new("a/b")),
+            (),
+            LabelPresence::KnownAbsent,
+        );
+        let mut visited_paths = BTreeSet::new();
+        trie.walk(&TriePath::from(Path::new("a/b/c")), &mut |path, _nodes| {
+            // Assert that we only ever visit a path once.
+            assert!(visited_paths.insert(path.clone()));
+            ControlFlow::Continue(())
+        });
+        assert_eq!(visited_paths.len(), 3);
+        trie.remove(&TriePath::from(Path::new("a/b/")));
+        let mut visited_paths = BTreeSet::new();
+        trie.walk(&TriePath::from(Path::new("a/b/c")), &mut |path, _nodes| {
+            // Assert that we only ever visit a path once.
+            assert!(visited_paths.insert(path.clone()));
+            ControlFlow::Continue(())
+        });
+        assert_eq!(visited_paths.len(), 1);
+        assert_eq!(
+            visited_paths.into_iter().next().unwrap().as_ref(),
+            Path::new("a/")
+        );
+    }
+}

crates/project/src/project_tree/server_tree.rs ๐Ÿ”—

@@ -0,0 +1,440 @@
+//! This module defines an LSP Tree.
+//!
+//! An LSP Tree is responsible for determining which language servers apply to a given project path.
+//!
+//! ## RPC
+//! LSP Tree is transparent to RPC peers; when clients ask host to spawn a new language server, the host will perform LSP Tree lookup for provided path; it may decide
+//! to reuse existing language server. The client maintains it's own LSP Tree that is a subset of host LSP Tree. Done this way, the client does not need to
+//! ask about suitable language server for each path it interacts with; it can resolve most of the queries locally.
+//! This module defines a Project Tree.
+
+use std::{
+    collections::{BTreeMap, BTreeSet},
+    path::Path,
+    sync::{Arc, Weak},
+};
+
+use collections::{HashMap, IndexMap};
+use gpui::{App, AppContext, Entity, Subscription};
+use itertools::Itertools;
+use language::{
+    language_settings::AllLanguageSettings, Attach, LanguageName, LanguageRegistry,
+    LspAdapterDelegate,
+};
+use lsp::LanguageServerName;
+use settings::{Settings, SettingsLocation, WorktreeId};
+use std::sync::OnceLock;
+
+use crate::{project_settings::LspSettings, LanguageServerId, ProjectPath};
+
+use super::{AdapterWrapper, ProjectTree, ProjectTreeEvent};
+
+#[derive(Debug, Default)]
+struct ServersForWorktree {
+    roots: BTreeMap<
+        Arc<Path>,
+        BTreeMap<LanguageServerName, (Arc<InnerTreeNode>, BTreeSet<LanguageName>)>,
+    >,
+}
+
+pub struct LanguageServerTree {
+    project_tree: Entity<ProjectTree>,
+    instances: BTreeMap<WorktreeId, ServersForWorktree>,
+    attach_kind_cache: HashMap<LanguageServerName, Attach>,
+    languages: Arc<LanguageRegistry>,
+    _subscriptions: Subscription,
+}
+
+/// A node in language server tree represents either:
+/// - A language server that has already been initialized/updated for a given project
+/// - A soon-to-be-initialized language server.
+#[derive(Clone)]
+pub(crate) struct LanguageServerTreeNode(Weak<InnerTreeNode>);
+
+/// Describes a request to launch a language server.
+#[derive(Debug)]
+pub(crate) struct LaunchDisposition<'a> {
+    pub(crate) server_name: &'a LanguageServerName,
+    pub(crate) attach: Attach,
+    pub(crate) path: ProjectPath,
+    pub(crate) settings: Arc<LspSettings>,
+}
+
+impl<'a> From<&'a InnerTreeNode> for LaunchDisposition<'a> {
+    fn from(value: &'a InnerTreeNode) -> Self {
+        LaunchDisposition {
+            server_name: &value.name,
+            attach: value.attach,
+            path: value.path.clone(),
+            settings: value.settings.clone(),
+        }
+    }
+}
+impl LanguageServerTreeNode {
+    /// Returns a language server ID for this node if there is one.
+    /// Returns None if this node has not been initialized yet or it is no longer in the tree.
+    pub(crate) fn server_id(&self) -> Option<LanguageServerId> {
+        self.0.upgrade()?.id.get().copied()
+    }
+    /// Returns a language server ID for this node if it has already been initialized; otherwise runs the provided closure to initialize the language server node in a tree.
+    /// May return None if the node no longer belongs to the server tree it was created in.
+    pub(crate) fn server_id_or_init(
+        &self,
+        init: impl FnOnce(LaunchDisposition) -> LanguageServerId,
+    ) -> Option<LanguageServerId> {
+        let this = self.0.upgrade()?;
+        Some(
+            *this
+                .id
+                .get_or_init(|| init(LaunchDisposition::from(&*this))),
+        )
+    }
+}
+
+impl From<Weak<InnerTreeNode>> for LanguageServerTreeNode {
+    fn from(weak: Weak<InnerTreeNode>) -> Self {
+        LanguageServerTreeNode(weak)
+    }
+}
+
+#[derive(Debug)]
+struct InnerTreeNode {
+    id: OnceLock<LanguageServerId>,
+    name: LanguageServerName,
+    attach: Attach,
+    path: ProjectPath,
+    settings: Arc<LspSettings>,
+}
+
+impl InnerTreeNode {
+    fn new(
+        name: LanguageServerName,
+        attach: Attach,
+        path: ProjectPath,
+        settings: impl Into<Arc<LspSettings>>,
+    ) -> Self {
+        InnerTreeNode {
+            id: Default::default(),
+            name,
+            attach,
+            path,
+            settings: settings.into(),
+        }
+    }
+}
+
+/// Determines how the list of adapters to query should be constructed.
+pub(crate) enum AdapterQuery<'a> {
+    /// Search for roots of all adapters associated with a given language name.
+    Language(&'a LanguageName),
+    /// Search for roots of adapter with a given name.
+    Adapter(&'a LanguageServerName),
+}
+
+impl LanguageServerTree {
+    pub(crate) fn new(
+        project_tree: Entity<ProjectTree>,
+        languages: Arc<LanguageRegistry>,
+        cx: &mut App,
+    ) -> Entity<Self> {
+        cx.new(|cx| Self {
+            _subscriptions: cx.subscribe(
+                &project_tree,
+                |_: &mut Self, _, event, _| {
+                    if event == &ProjectTreeEvent::Cleared {}
+                },
+            ),
+            project_tree,
+            instances: Default::default(),
+            attach_kind_cache: Default::default(),
+            languages,
+        })
+    }
+    /// Memoize calls to attach_kind on LspAdapter (which might be a WASM extension, thus ~expensive to call).
+    fn attach_kind(&mut self, adapter: &AdapterWrapper) -> Attach {
+        *self
+            .attach_kind_cache
+            .entry(adapter.0.name.clone())
+            .or_insert_with(|| adapter.0.attach_kind())
+    }
+
+    /// Get all language server root points for a given path and language; the language servers might already be initialized at a given path.
+    pub(crate) fn get<'a>(
+        &'a mut self,
+        path: ProjectPath,
+        query: AdapterQuery<'_>,
+        delegate: Arc<dyn LspAdapterDelegate>,
+        cx: &mut App,
+    ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
+        let settings_location = SettingsLocation {
+            worktree_id: path.worktree_id,
+            path: &path.path,
+        };
+        let adapters = match query {
+            AdapterQuery::Language(language_name) => {
+                self.adapters_for_language(settings_location, language_name, cx)
+            }
+            AdapterQuery::Adapter(language_server_name) => IndexMap::from_iter(
+                self.adapter_for_name(language_server_name)
+                    .map(|adapter| (adapter, (LspSettings::default(), BTreeSet::new()))),
+            ),
+        };
+        self.get_with_adapters(path, adapters, delegate, cx)
+    }
+
+    fn get_with_adapters<'a>(
+        &'a mut self,
+        path: ProjectPath,
+        adapters: IndexMap<AdapterWrapper, (LspSettings, BTreeSet<LanguageName>)>,
+        delegate: Arc<dyn LspAdapterDelegate>,
+        cx: &mut App,
+    ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
+        let worktree_id = path.worktree_id;
+        #[allow(clippy::mutable_key_type)]
+        let mut roots = self.project_tree.update(cx, |this, cx| {
+            this.root_for_path(
+                path,
+                adapters
+                    .iter()
+                    .map(|(adapter, _)| adapter.0.clone())
+                    .collect(),
+                delegate,
+                cx,
+            )
+        });
+        let mut root_path = None;
+        // Backwards-compat: Fill in any adapters for which we did not detect the root as having the project root at the root of a worktree.
+        for (adapter, _) in adapters.iter() {
+            roots.entry(adapter.clone()).or_insert_with(|| {
+                root_path
+                    .get_or_insert_with(|| ProjectPath {
+                        worktree_id,
+                        path: Arc::from("".as_ref()),
+                    })
+                    .clone()
+            });
+        }
+
+        roots
+            .into_iter()
+            .filter_map(move |(adapter, root_path)| {
+                let attach = self.attach_kind(&adapter);
+                let (index, _, (settings, new_languages)) = adapters.get_full(&adapter)?;
+                let inner_node = self
+                    .instances
+                    .entry(root_path.worktree_id)
+                    .or_default()
+                    .roots
+                    .entry(root_path.path.clone())
+                    .or_default()
+                    .entry(adapter.0.name.clone());
+                let (node, languages) = inner_node.or_insert_with(move || {
+                    (
+                        Arc::new(InnerTreeNode::new(
+                            adapter.0.name(),
+                            attach,
+                            root_path,
+                            settings.clone(),
+                        )),
+                        Default::default(),
+                    )
+                });
+                languages.extend(new_languages.iter().cloned());
+                Some((index, Arc::downgrade(&node).into()))
+            })
+            .sorted_by_key(|(index, _)| *index)
+            .map(|(_, node)| node)
+    }
+
+    fn adapter_for_name(&self, name: &LanguageServerName) -> Option<AdapterWrapper> {
+        self.languages.adapter_for_name(name).map(AdapterWrapper)
+    }
+
+    fn adapters_for_language(
+        &self,
+        settings_location: SettingsLocation,
+        language_name: &LanguageName,
+        cx: &App,
+    ) -> IndexMap<AdapterWrapper, (LspSettings, BTreeSet<LanguageName>)> {
+        let settings = AllLanguageSettings::get(Some(settings_location), cx).language(
+            Some(settings_location),
+            Some(language_name),
+            cx,
+        );
+        if !settings.enable_language_server {
+            return Default::default();
+        }
+        let available_lsp_adapters = self.languages.lsp_adapters(&language_name);
+        let available_language_servers = available_lsp_adapters
+            .iter()
+            .map(|lsp_adapter| lsp_adapter.name.clone())
+            .collect::<Vec<_>>();
+
+        let desired_language_servers =
+            settings.customized_language_servers(&available_language_servers);
+        let adapters_with_settings = desired_language_servers
+            .into_iter()
+            .filter_map(|desired_adapter| {
+                let adapter = if let Some(adapter) = available_lsp_adapters
+                    .iter()
+                    .find(|adapter| adapter.name == desired_adapter)
+                {
+                    Some(adapter.clone())
+                } else if let Some(adapter) =
+                    self.languages.load_available_lsp_adapter(&desired_adapter)
+                {
+                    self.languages
+                        .register_lsp_adapter(language_name.clone(), adapter.adapter.clone());
+                    Some(adapter)
+                } else {
+                    None
+                }?;
+                let adapter_settings = crate::lsp_store::language_server_settings_for(
+                    settings_location,
+                    &adapter.name,
+                    cx,
+                )
+                .cloned()
+                .unwrap_or_default();
+                Some((
+                    AdapterWrapper(adapter),
+                    (
+                        adapter_settings,
+                        BTreeSet::from_iter([language_name.clone()]),
+                    ),
+                ))
+            })
+            .collect::<IndexMap<_, _>>();
+        // 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_name,
+            adapters_with_settings
+                .keys()
+                .map(|wrapper| wrapper.0.clone())
+                .collect(),
+        );
+
+        adapters_with_settings
+    }
+
+    // Rebasing a tree:
+    // - Clears it out
+    // - Provides you with the indirect access to the old tree while you're reinitializing a new one (by querying it).
+    pub(crate) fn rebase(&mut self) -> ServerTreeRebase<'_> {
+        ServerTreeRebase::new(self)
+    }
+
+    /// Remove nodes with a given ID from the tree.
+    pub(crate) fn remove_nodes(&mut self, ids: &BTreeSet<LanguageServerId>) {
+        for (_, servers) in &mut self.instances {
+            for (_, nodes) in &mut servers.roots {
+                nodes.retain(|_, (node, _)| node.id.get().map_or(true, |id| !ids.contains(&id)));
+            }
+        }
+    }
+}
+
+pub(crate) struct ServerTreeRebase<'a> {
+    old_contents: BTreeMap<WorktreeId, ServersForWorktree>,
+    new_tree: &'a mut LanguageServerTree,
+    /// All server IDs seen in the old tree.
+    all_server_ids: BTreeMap<LanguageServerId, LanguageServerName>,
+    /// Server IDs we've preserved for a new iteration of the tree. `all_server_ids - rebased_server_ids` is the
+    /// set of server IDs that can be shut down.
+    rebased_server_ids: BTreeSet<LanguageServerId>,
+}
+
+impl<'tree> ServerTreeRebase<'tree> {
+    fn new(new_tree: &'tree mut LanguageServerTree) -> Self {
+        let old_contents = std::mem::take(&mut new_tree.instances);
+        new_tree.attach_kind_cache.clear();
+        let all_server_ids = old_contents
+            .values()
+            .flat_map(|nodes| {
+                nodes.roots.values().flat_map(|servers| {
+                    servers.values().filter_map(|server| {
+                        server
+                            .0
+                            .id
+                            .get()
+                            .copied()
+                            .map(|id| (id, server.0.name.clone()))
+                    })
+                })
+            })
+            .collect();
+        Self {
+            old_contents,
+            new_tree,
+            all_server_ids,
+            rebased_server_ids: BTreeSet::new(),
+        }
+    }
+
+    pub(crate) fn get<'a>(
+        &'a mut self,
+        path: ProjectPath,
+        query: AdapterQuery<'_>,
+        delegate: Arc<dyn LspAdapterDelegate>,
+        cx: &mut App,
+    ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
+        let settings_location = SettingsLocation {
+            worktree_id: path.worktree_id,
+            path: &path.path,
+        };
+        let adapters = match query {
+            AdapterQuery::Language(language_name) => {
+                self.new_tree
+                    .adapters_for_language(settings_location, language_name, cx)
+            }
+            AdapterQuery::Adapter(language_server_name) => IndexMap::from_iter(
+                self.new_tree
+                    .adapter_for_name(language_server_name)
+                    .map(|adapter| (adapter, (LspSettings::default(), BTreeSet::new()))),
+            ),
+        };
+
+        self.new_tree
+            .get_with_adapters(path, adapters, delegate, cx)
+            .filter_map(|node| {
+                // Inspect result of the query and initialize it ourselves before
+                // handing it off to the caller.
+                let disposition = node.0.upgrade()?;
+
+                if disposition.id.get().is_some() {
+                    return Some(node);
+                }
+                let Some((existing_node, _)) = self
+                    .old_contents
+                    .get(&disposition.path.worktree_id)
+                    .and_then(|worktree_nodes| worktree_nodes.roots.get(&disposition.path.path))
+                    .and_then(|roots| roots.get(&disposition.name))
+                    .filter(|(old_node, _)| {
+                        disposition.attach == old_node.attach
+                            && disposition.settings == old_node.settings
+                    })
+                else {
+                    return Some(node);
+                };
+                if let Some(existing_id) = existing_node.id.get() {
+                    self.rebased_server_ids.insert(*existing_id);
+                    disposition.id.set(*existing_id).ok();
+                }
+
+                Some(node)
+            })
+    }
+
+    /// Returns IDs of servers that are no longer referenced (and can be shut down).
+    pub(crate) fn finish(self) -> BTreeMap<LanguageServerId, LanguageServerName> {
+        self.all_server_ids
+            .into_iter()
+            .filter(|(id, _)| !self.rebased_server_ids.contains(id))
+            .collect()
+    }
+}

crates/project_panel/src/project_panel.rs ๐Ÿ”—

@@ -7234,8 +7234,8 @@ mod tests {
         init_test(cx);
 
         let fs = FakeFs::new(cx.executor().clone());
-        fs.as_fake().insert_tree("/root", json!({})).await;
-        let project = Project::test(fs, ["/root".as_ref()], cx).await;
+        fs.as_fake().insert_tree(path!("/root"), json!({})).await;
+        let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
         let workspace =
             cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
         let cx = &mut VisualTestContext::from_window(*workspace, cx);
@@ -7258,7 +7258,7 @@ mod tests {
             .unwrap();
 
         cx.executor().run_until_parked();
-        cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/new")));
+        cx.simulate_new_path_selection(|_| Some(PathBuf::from(path!("/root/new"))));
         save_task.await.unwrap();
 
         // Rename the file

crates/proto/proto/zed.proto ๐Ÿ”—

@@ -789,6 +789,7 @@ message Symbol {
     PointUtf16 start = 7;
     PointUtf16 end = 8;
     bytes signature = 9;
+    uint64 language_server_id = 10;
 }
 
 message OpenBufferForSymbol {

crates/recent_projects/src/recent_projects.rs ๐Ÿ”—

@@ -671,7 +671,7 @@ mod tests {
                     }];
                     delegate.set_workspaces(vec![(
                         WorkspaceId::default(),
-                        SerializedWorkspaceLocation::from_local_paths(vec!["/test/path/"]),
+                        SerializedWorkspaceLocation::from_local_paths(vec![path!("/test/path/")]),
                     )]);
                 });
             })

crates/tab_switcher/src/tab_switcher_tests.rs ๐Ÿ”—

@@ -117,7 +117,7 @@ async fn test_open_item_on_modifiers_release(cx: &mut gpui::TestAppContext) {
         .fs
         .as_fake()
         .insert_tree(
-            "/root",
+            path!("/root"),
             json!({
                 "1.txt": "First file",
                 "2.txt": "Second file",
@@ -125,7 +125,7 @@ async fn test_open_item_on_modifiers_release(cx: &mut gpui::TestAppContext) {
         )
         .await;
 
-    let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
+    let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
     let (workspace, cx) =
         cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 

crates/tasks_ui/src/modal.rs ๐Ÿ”—

@@ -1020,7 +1020,7 @@ mod tests {
 
         let _rs_file = workspace
             .update_in(cx, |workspace, window, cx| {
-                workspace.open_abs_path(PathBuf::from("/dir/b.rs"), true, window, cx)
+                workspace.open_abs_path(PathBuf::from(path!("/dir/b.rs")), true, window, cx)
             })
             .await
             .unwrap();

crates/zed/src/zed.rs ๐Ÿ”—

@@ -1711,7 +1711,7 @@ mod tests {
             .fs
             .as_fake()
             .insert_tree(
-                "/root",
+                path!("/root"),
                 json!({
                     "a": {
                     },
@@ -1721,7 +1721,7 @@ mod tests {
 
         cx.update(|cx| {
             open_paths(
-                &[PathBuf::from("/root/a/new")],
+                &[PathBuf::from(path!("/root/a/new"))],
                 app_state.clone(),
                 workspace::OpenOptions::default(),
                 cx,

crates/zed/src/zed/quick_action_bar.rs ๐Ÿ”—

@@ -97,10 +97,11 @@ impl Render for QuickActionBar {
             show_inline_completions,
             inline_completion_enabled,
         ) = {
+            let supports_inlay_hints =
+                editor.update(cx, |editor, cx| editor.supports_inlay_hints(cx));
             let editor = editor.read(cx);
             let selection_menu_enabled = editor.selection_menu_enabled(cx);
             let inlay_hints_enabled = editor.inlay_hints_enabled();
-            let supports_inlay_hints = editor.supports_inlay_hints(cx);
             let git_blame_inline_enabled = editor.git_blame_inline_enabled();
             let show_git_blame_gutter = editor.show_git_blame_gutter();
             let auto_signature_help_enabled = editor.auto_signature_help_enabled(cx);
@@ -472,13 +473,22 @@ impl ToolbarItemView for QuickActionBar {
             self._inlay_hints_enabled_subscription.take();
 
             if let Some(editor) = active_item.downcast::<Editor>() {
-                let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
-                let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
+                let (mut inlay_hints_enabled, mut supports_inlay_hints) =
+                    editor.update(cx, |editor, cx| {
+                        (
+                            editor.inlay_hints_enabled(),
+                            editor.supports_inlay_hints(cx),
+                        )
+                    });
                 self._inlay_hints_enabled_subscription =
                     Some(cx.observe(&editor, move |_, editor, cx| {
-                        let editor = editor.read(cx);
-                        let new_inlay_hints_enabled = editor.inlay_hints_enabled();
-                        let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
+                        let (new_inlay_hints_enabled, new_supports_inlay_hints) =
+                            editor.update(cx, |editor, cx| {
+                                (
+                                    editor.inlay_hints_enabled(),
+                                    editor.supports_inlay_hints(cx),
+                                )
+                            });
                         let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
                             || supports_inlay_hints != new_supports_inlay_hints;
                         inlay_hints_enabled = new_inlay_hints_enabled;