Reuse existing language servers for invisible worktrees (#30707)

Closes https://github.com/zed-industries/zed/issues/20767

Before:


https://github.com/user-attachments/assets/6438eb26-796a-4586-9b20-f49d9a133624


After:



https://github.com/user-attachments/assets/b3fc2f8b-2873-443f-8d80-ab4a35cf0c09



Release Notes:

- Fixed external files spawning extra language servers
This commit is contained in:
Kirill Bulatov 2025-05-14 18:24:17 +02:00 committed by GitHub
parent ef511976be
commit fcfe4e2c14
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 400 additions and 107 deletions

View file

@ -52,7 +52,7 @@ use util::{
uri,
};
use workspace::{
CloseAllItems, CloseInactiveItems, NavigationEntry, ViewId,
CloseActiveItem, CloseAllItems, CloseInactiveItems, NavigationEntry, OpenOptions, ViewId,
item::{FollowEvent, FollowableItem, Item, ItemHandle},
};
@ -19867,6 +19867,156 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/root"),
json!({
"a": {
"main.rs": "fn main() {}",
},
"foo": {
"bar": {
"external_file.rs": "pub mod external {}",
}
}
}),
)
.await;
let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
let _fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
..FakeLspAdapter::default()
},
);
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().update(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
})
});
let assert_language_servers_count =
|expected: usize, context: &str, cx: &mut VisualTestContext| {
project.update(cx, |project, cx| {
let current = project
.lsp_store()
.read(cx)
.as_local()
.unwrap()
.language_servers
.len();
assert_eq!(expected, current, "{context}");
});
};
assert_language_servers_count(
0,
"No servers should be running before any file is open",
cx,
);
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
let main_editor = workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_path(
(worktree_id, "main.rs"),
Some(pane.downgrade()),
true,
window,
cx,
)
})
.unwrap()
.await
.downcast::<Editor>()
.unwrap();
pane.update(cx, |pane, cx| {
let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
open_editor.update(cx, |editor, cx| {
assert_eq!(
editor.display_text(cx),
"fn main() {}",
"Original main.rs text on initial open",
);
});
assert_eq!(open_editor, main_editor);
});
assert_language_servers_count(1, "First *.rs file starts a language server", cx);
let external_editor = workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_abs_path(
PathBuf::from("/root/foo/bar/external_file.rs"),
OpenOptions::default(),
window,
cx,
)
})
.await
.expect("opening external file")
.downcast::<Editor>()
.expect("downcasted external file's open element to editor");
pane.update(cx, |pane, cx| {
let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
open_editor.update(cx, |editor, cx| {
assert_eq!(
editor.display_text(cx),
"pub mod external {}",
"External file is open now",
);
});
assert_eq!(open_editor, external_editor);
});
assert_language_servers_count(
1,
"Second, external, *.rs file should join the existing server",
cx,
);
pane.update_in(cx, |pane, window, cx| {
pane.close_active_item(&CloseActiveItem::default(), window, cx)
})
.unwrap()
.await
.unwrap();
pane.update_in(cx, |pane, window, cx| {
pane.navigate_backward(window, cx);
});
cx.run_until_parked();
pane.update(cx, |pane, cx| {
let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
open_editor.update(cx, |editor, cx| {
assert_eq!(
editor.display_text(cx),
"pub mod external {}",
"External file is open now",
);
});
});
assert_language_servers_count(
1,
"After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
cx,
);
cx.update(|_, cx| {
workspace::reload(&workspace::Reload::default(), cx);
});
assert_language_servers_count(
1,
"After reloading the worktree with local and external files opened, only one project should be started",
cx,
);
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point