mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Ensure language servers from extension properly start on workspace restoration (#51308)
Closes #49877 Before you mark this PR as ready for review, make sure that you have: - [x] Added a solid test coverage and/or screenshots from doing manual testing - [x] Done a self-review taking into account security and performance aspects - [ ] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - Fixed extension language servers not starting when Zed launches with files already open from a restored session.
This commit is contained in:
parent
7c45d93e5e
commit
11cfb9e330
3 changed files with 106 additions and 2 deletions
|
|
@ -319,6 +319,8 @@ impl LanguageRegistry {
|
|||
state
|
||||
.all_lsp_adapters
|
||||
.insert(cached.name.clone(), cached.clone());
|
||||
state.version += 1;
|
||||
*state.subscription.0.borrow_mut() = ();
|
||||
}
|
||||
|
||||
/// Register a fake language server and adapter
|
||||
|
|
@ -354,6 +356,8 @@ impl LanguageRegistry {
|
|||
state
|
||||
.all_lsp_adapters
|
||||
.insert(cached_adapter.name(), cached_adapter);
|
||||
state.version += 1;
|
||||
*state.subscription.0.borrow_mut() = ();
|
||||
}
|
||||
|
||||
/// Register a fake language server (without the adapter)
|
||||
|
|
|
|||
|
|
@ -4747,6 +4747,7 @@ impl LspStore {
|
|||
|
||||
this.update(cx, |this, cx| {
|
||||
let mut plain_text_buffers = Vec::new();
|
||||
let mut buffers_with_language = Vec::new();
|
||||
let mut buffers_with_unknown_injections = Vec::new();
|
||||
for handle in this.buffer_store.read(cx).buffers() {
|
||||
let buffer = handle.read(cx);
|
||||
|
|
@ -4754,8 +4755,11 @@ impl LspStore {
|
|||
|| buffer.language() == Some(&*language::PLAIN_TEXT)
|
||||
{
|
||||
plain_text_buffers.push(handle);
|
||||
} else if buffer.contains_unknown_injections() {
|
||||
buffers_with_unknown_injections.push(handle);
|
||||
} else {
|
||||
if buffer.contains_unknown_injections() {
|
||||
buffers_with_unknown_injections.push(handle.clone());
|
||||
}
|
||||
buffers_with_language.push(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4785,6 +4789,24 @@ impl LspStore {
|
|||
}
|
||||
}
|
||||
|
||||
// Also register buffers that already have a language with
|
||||
// any newly-available language servers (e.g., from extensions
|
||||
// that finished loading after buffers were restored).
|
||||
if let Some(local) = this.as_local_mut() {
|
||||
for buffer in buffers_with_language {
|
||||
if local
|
||||
.registered_buffers
|
||||
.contains_key(&buffer.read(cx).remote_id())
|
||||
{
|
||||
local.register_buffer_with_language_servers(
|
||||
&buffer,
|
||||
HashSet::default(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for buffer in buffers_with_unknown_injections {
|
||||
buffer.update(cx, |buffer, cx| buffer.reparse(cx, false));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1915,6 +1915,84 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_late_lsp_adapter_registration(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"test.rs": "const A: i32 = 1;",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
|
||||
// Add the language first so the buffer gets assigned a language.
|
||||
language_registry.add(rust_lang());
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// Open a buffer — it gets assigned the Rust language but there is no LSP adapter yet.
|
||||
let (rust_buffer, _handle) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer_with_lsp(path!("/dir/test.rs"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
rust_buffer.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into()));
|
||||
});
|
||||
|
||||
// Now register the LSP adapter late (simulating an extension loading after startup).
|
||||
let mut fake_rust_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "the-rust-language-server",
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// The language server should start and receive a DidOpenTextDocument notification
|
||||
// for the already-open buffer.
|
||||
let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
|
||||
assert_eq!(
|
||||
fake_rust_server
|
||||
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await
|
||||
.text_document,
|
||||
lsp::TextDocumentItem {
|
||||
uri: lsp::Uri::from_file_path(path!("/dir/test.rs")).unwrap(),
|
||||
version: 0,
|
||||
text: "const A: i32 = 1;".to_string(),
|
||||
language_id: "rust".to_string(),
|
||||
}
|
||||
);
|
||||
|
||||
// The buffer should be configured with the language server's capabilities.
|
||||
rust_buffer.update(cx, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer
|
||||
.completion_triggers()
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
&[".".to_string(), "::".to_string()]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_language_server_relative_path(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
|
|
|||
Loading…
Reference in a new issue