Start to split out initialization and registration (#21787)

Still TODO:

* [x] Factor out `start_language_server` so we can call it on register
(instead of on detect language)
* [x] Only call register in singleton editors (or when
editing/go-to-definition etc. in a multibuffer?)
* [x] Refcount on register so we can unregister when no buffer remain
* [ ] (maybe) Stop language servers that are no longer needed after some
time

Release Notes:

- Fixed language servers starting when doing project search
- Fixed high CPU usage when ignoring warnings in the diagnostics view

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Cole <cole@zed.dev>
This commit is contained in:
Conrad Irwin 2024-12-11 14:05:10 -07:00 committed by GitHub
parent de89f8cf83
commit 13a81e454a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 2200 additions and 1848 deletions

View file

@ -1,4 +1,5 @@
use crate::{
lsp_store::OpenLspBufferHandle,
search::SearchQuery,
worktree_store::{WorktreeStore, WorktreeStoreEvent},
ProjectItem as _, ProjectPath,
@ -47,6 +48,7 @@ pub struct BufferStore {
struct SharedBuffer {
buffer: Model<Buffer>,
unstaged_changes: Option<Model<BufferChangeSet>>,
lsp_handle: Option<OpenLspBufferHandle>,
}
#[derive(Debug)]
@ -1571,6 +1573,21 @@ impl BufferStore {
})?
}
pub fn register_shared_lsp_handle(
&mut self,
peer_id: proto::PeerId,
buffer_id: BufferId,
handle: OpenLspBufferHandle,
) {
if let Some(shared_buffers) = self.shared_buffers.get_mut(&peer_id) {
if let Some(buffer) = shared_buffers.get_mut(&buffer_id) {
buffer.lsp_handle = Some(handle);
return;
}
}
debug_panic!("tried to register shared lsp handle, but buffer was not shared")
}
pub fn handle_synchronize_buffers(
&mut self,
envelope: TypedEnvelope<proto::SynchronizeBuffers>,
@ -1597,6 +1614,7 @@ impl BufferStore {
.or_insert_with(|| SharedBuffer {
buffer: buffer.clone(),
unstaged_changes: None,
lsp_handle: None,
});
let buffer = buffer.read(cx);
@ -2017,6 +2035,7 @@ impl BufferStore {
SharedBuffer {
buffer: buffer.clone(),
unstaged_changes: None,
lsp_handle: None,
},
);

File diff suppressed because it is too large Load diff

View file

@ -1254,6 +1254,10 @@ impl Project {
self.buffer_store.read(cx).buffers().collect()
}
pub fn environment(&self) -> &Model<ProjectEnvironment> {
&self.environment
}
pub fn cli_environment(&self, cx: &AppContext) -> Option<HashMap<String, String>> {
self.environment.read(cx).get_cli_environment()
}
@ -1843,6 +1847,19 @@ impl Project {
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn open_local_buffer_with_lsp(
&mut self,
abs_path: impl AsRef<Path>,
cx: &mut ModelContext<Self>,
) -> Task<Result<(Model<Buffer>, lsp_store::OpenLspBufferHandle)>> {
if let Some((worktree, relative_path)) = self.find_worktree(abs_path.as_ref(), cx) {
self.open_buffer_with_lsp((worktree.read(cx).id(), relative_path), cx)
} else {
Task::ready(Err(anyhow!("no such path")))
}
}
pub fn open_buffer(
&mut self,
path: impl Into<ProjectPath>,
@ -1857,6 +1874,23 @@ impl Project {
})
}
#[cfg(any(test, feature = "test-support"))]
pub fn open_buffer_with_lsp(
&mut self,
path: impl Into<ProjectPath>,
cx: &mut ModelContext<Self>,
) -> Task<Result<(Model<Buffer>, lsp_store::OpenLspBufferHandle)>> {
let buffer = self.open_buffer(path, cx);
let lsp_store = self.lsp_store().clone();
cx.spawn(|_, mut cx| async move {
let buffer = buffer.await?;
let handle = lsp_store.update(&mut cx, |lsp_store, cx| {
lsp_store.register_buffer_with_language_servers(&buffer, cx)
})?;
Ok((buffer, handle))
})
}
pub fn open_unstaged_changes(
&mut self,
buffer: Model<Buffer>,

View file

@ -442,17 +442,17 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
);
// Open a buffer without an associated language server.
let toml_buffer = project
let (toml_buffer, _handle) = project
.update(cx, |project, cx| {
project.open_local_buffer("/the-root/Cargo.toml", cx)
project.open_local_buffer_with_lsp("/the-root/Cargo.toml", cx)
})
.await
.unwrap();
// Open a buffer with an associated language server before the language for it has been loaded.
let rust_buffer = project
let (rust_buffer, _handle2) = project
.update(cx, |project, cx| {
project.open_local_buffer("/the-root/test.rs", cx)
project.open_local_buffer_with_lsp("/the-root/test.rs", cx)
})
.await
.unwrap();
@ -513,9 +513,9 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
);
// Open a third buffer with a different associated language server.
let json_buffer = project
let (json_buffer, _json_handle) = project
.update(cx, |project, cx| {
project.open_local_buffer("/the-root/package.json", cx)
project.open_local_buffer_with_lsp("/the-root/package.json", cx)
})
.await
.unwrap();
@ -550,9 +550,9 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
// When opening another buffer whose language server is already running,
// it is also configured based on the existing language server's capabilities.
let rust_buffer2 = project
let (rust_buffer2, _handle4) = project
.update(cx, |project, cx| {
project.open_local_buffer("/the-root/test2.rs", cx)
project.open_local_buffer_with_lsp("/the-root/test2.rs", cx)
})
.await
.unwrap();
@ -765,7 +765,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
);
// Close notifications are reported only to servers matching the buffer's language.
cx.update(|_| drop(json_buffer));
cx.update(|_| drop(_json_handle));
let close_message = lsp::DidCloseTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(
lsp::Url::from_file_path("/the-root/package.json").unwrap(),
@ -827,9 +827,9 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
cx.executor().run_until_parked();
// Start the language server by opening a buffer with a compatible file extension.
let _buffer = project
let _ = project
.update(cx, |project, cx| {
project.open_local_buffer("/the-root/src/a.rs", cx)
project.open_local_buffer_with_lsp("/the-root/src/a.rs", cx)
})
.await
.unwrap();
@ -1239,8 +1239,10 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
let worktree_id = project.update(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
// Cause worktree to start the fake language server
let _buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
let _ = project
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/b.rs", cx)
})
.await
.unwrap();
@ -1259,6 +1261,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
fake_server
.start_progress(format!("{}/0", progress_token))
.await;
assert_eq!(events.next().await.unwrap(), Event::RefreshInlayHints);
assert_eq!(
events.next().await.unwrap(),
Event::DiskBasedDiagnosticsStarted {
@ -1365,8 +1368,10 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
let worktree_id = project.update(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
let (buffer, _handle) = project
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/a.rs", cx)
})
.await
.unwrap();
@ -1390,6 +1395,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
Some(worktree_id)
)
);
assert_eq!(events.next().await.unwrap(), Event::RefreshInlayHints);
fake_server.start_progress(progress_token).await;
assert_eq!(
events.next().await.unwrap(),
@ -1438,8 +1444,10 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
let (buffer, _) = project
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/a.rs", cx)
})
.await
.unwrap();
@ -1517,8 +1525,10 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
let (buffer, _handle) = project
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/a.rs", cx)
})
.await
.unwrap();
@ -1565,8 +1575,10 @@ async fn test_cancel_language_server_work(cx: &mut gpui::TestAppContext) {
},
);
let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
let (buffer, _handle) = project
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/a.rs", cx)
})
.await
.unwrap();
@ -1634,11 +1646,15 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
language_registry.add(js_lang());
let _rs_buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/a.rs", cx)
})
.await
.unwrap();
let _js_buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx))
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/b.js", cx)
})
.await
.unwrap();
@ -1734,6 +1750,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
let project = Project::test(fs, ["/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());
@ -1750,6 +1767,10 @@ 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 mut fake_server = fake_servers.next().await.unwrap();
let open_notification = fake_server
.receive_notification::<lsp::notification::DidOpenTextDocument>()
@ -2162,8 +2183,10 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
let (buffer, _handle) = project
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/a.rs", cx)
})
.await
.unwrap();
@ -2533,8 +2556,10 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
let (buffer, _handle) = project
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/b.rs", cx)
})
.await
.unwrap();
@ -2638,8 +2663,8 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
},
);
let buffer = project
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
let (buffer, _handle) = project
.update(cx, |p, cx| p.open_local_buffer_with_lsp("/dir/a.ts", cx))
.await
.unwrap();
@ -2730,8 +2755,8 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
},
);
let buffer = project
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
let (buffer, _handle) = project
.update(cx, |p, cx| p.open_local_buffer_with_lsp("/dir/a.ts", cx))
.await
.unwrap();
@ -2793,8 +2818,8 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
},
);
let buffer = project
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
let (buffer, _handle) = project
.update(cx, |p, cx| p.open_local_buffer_with_lsp("/dir/a.ts", cx))
.await
.unwrap();
@ -3984,7 +4009,7 @@ async fn test_lsp_rename_notifications(cx: &mut gpui::TestAppContext) {
let _ = project
.update(cx, |project, cx| {
project.open_local_buffer("/dir/one.rs", cx)
project.open_local_buffer_with_lsp("/dir/one.rs", cx)
})
.await
.unwrap();
@ -4086,9 +4111,9 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
},
);
let buffer = project
let (buffer, _handle) = project
.update(cx, |project, cx| {
project.open_local_buffer("/dir/one.rs", cx)
project.open_local_buffer_with_lsp("/dir/one.rs", cx)
})
.await
.unwrap();
@ -4951,8 +4976,8 @@ async fn test_multiple_language_server_hovers(cx: &mut gpui::TestAppContext) {
),
];
let buffer = project
.update(cx, |p, cx| p.open_local_buffer("/dir/a.tsx", cx))
let (buffer, _handle) = project
.update(cx, |p, cx| p.open_local_buffer_with_lsp("/dir/a.tsx", cx))
.await
.unwrap();
cx.executor().run_until_parked();
@ -5060,8 +5085,8 @@ async fn test_hovers_with_empty_parts(cx: &mut gpui::TestAppContext) {
},
);
let buffer = project
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
let (buffer, _handle) = project
.update(cx, |p, cx| p.open_local_buffer_with_lsp("/dir/a.ts", cx))
.await
.unwrap();
cx.executor().run_until_parked();
@ -5130,8 +5155,8 @@ async fn test_code_actions_only_kinds(cx: &mut gpui::TestAppContext) {
},
);
let buffer = project
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
let (buffer, _handle) = project
.update(cx, |p, cx| p.open_local_buffer_with_lsp("/dir/a.ts", cx))
.await
.unwrap();
cx.executor().run_until_parked();
@ -5251,8 +5276,8 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) {
),
];
let buffer = project
.update(cx, |p, cx| p.open_local_buffer("/dir/a.tsx", cx))
let (buffer, _handle) = project
.update(cx, |p, cx| p.open_local_buffer_with_lsp("/dir/a.tsx", cx))
.await
.unwrap();
cx.executor().run_until_parked();