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

@ -5113,9 +5113,11 @@ fn make_lsp_adapter_delegate(
return Ok(None::<Arc<dyn LspAdapterDelegate>>);
};
let http_client = project.client().http_client().clone();
project.lsp_store().update(cx, |lsp_store, cx| {
project.lsp_store().update(cx, |_, cx| {
Ok(Some(LocalLspAdapterDelegate::new(
lsp_store,
project.languages().clone(),
project.environment(),
cx.weak_model(),
&worktree,
http_client,
project.fs().clone(),

View file

@ -310,6 +310,9 @@ impl Server {
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
.add_request_handler(forward_read_only_project_request::<proto::GitBranches>)
.add_request_handler(forward_read_only_project_request::<proto::GetStagedText>)
.add_request_handler(
forward_mutating_project_request::<proto::RegisterBufferWithLanguageServers>,
)
.add_request_handler(forward_mutating_project_request::<proto::UpdateGitBranch>)
.add_request_handler(forward_mutating_project_request::<proto::GetCompletions>)
.add_request_handler(

View file

@ -994,10 +994,12 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
}),
)
.await;
let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
let _buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.update(cx_a, |p, cx| {
p.open_local_buffer_with_lsp("/dir/main.rs", cx)
})
.await
.unwrap();
@ -1587,7 +1589,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
})
.await
.unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap();
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@ -1597,6 +1598,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.downcast::<Editor>()
.unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap();
// Set up the language server to return an additional inlay hint on each request.
let edits_made = Arc::new(AtomicUsize::new(0));
let closure_edits_made = Arc::clone(&edits_made);

View file

@ -3891,13 +3891,7 @@ async fn test_collaborating_with_diagnostics(
// Cause the language server to start.
let _buffer = project_a
.update(cx_a, |project, cx| {
project.open_buffer(
ProjectPath {
worktree_id,
path: Path::new("other.rs").into(),
},
cx,
)
project.open_local_buffer_with_lsp("/a/other.rs", cx)
})
.await
.unwrap();
@ -4176,7 +4170,9 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
// Join the project as client B and open all three files.
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx))
project_b.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, file_name), cx)
})
}))
.await
.unwrap();
@ -4230,7 +4226,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
cx.subscribe(&project_b, move |_, _, event, cx| {
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
disk_based_diagnostics_finished.store(true, SeqCst);
for buffer in &guest_buffers {
for (buffer, _) in &guest_buffers {
assert_eq!(
buffer
.read(cx)
@ -4351,7 +4347,6 @@ async fn test_formatting_buffer(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
executor.allow_parking();
let mut server = TestServer::start(executor.clone()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@ -4379,10 +4374,16 @@ 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 open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
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 fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
Ok(Some(vec![
@ -4431,6 +4432,8 @@ async fn test_formatting_buffer(
});
});
});
executor.allow_parking();
project_b
.update(cx_b, |project, cx| {
project.format(
@ -4503,8 +4506,12 @@ async fn test_prettier_formatting_buffer(
.await
.unwrap();
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
let (buffer_b, _) = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
})
.await
.unwrap();
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
@ -4620,8 +4627,12 @@ async fn test_definition(
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file on client B.
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
let (buffer_b, _handle) = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
})
.await
.unwrap();
// Request the definition of a symbol as the guest.
let fake_language_server = fake_language_servers.next().await.unwrap();
@ -4765,8 +4776,12 @@ async fn test_references(
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file on client B.
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
let (buffer_b, _handle) = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
})
.await
.unwrap();
// Request references to a symbol as the guest.
let fake_language_server = fake_language_servers.next().await.unwrap();
@ -5012,8 +5027,12 @@ async fn test_document_highlights(
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file on client B.
let open_b = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_b).await.unwrap();
let (buffer_b, _handle) = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
})
.await
.unwrap();
// Request document highlights as the guest.
let fake_language_server = fake_language_servers.next().await.unwrap();
@ -5130,8 +5149,12 @@ async fn test_lsp_hover(
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file as the guest
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
let (buffer_b, _handle) = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
})
.await
.unwrap();
let mut servers_with_hover_requests = HashMap::default();
for i in 0..language_server_names.len() {
@ -5306,9 +5329,12 @@ async fn test_project_symbols(
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Cause the language server to start.
let open_buffer_task =
project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx));
let _buffer = cx_b.executor().spawn(open_buffer_task).await.unwrap();
let _buffer = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
})
.await
.unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(|_, _| async move {
@ -5400,8 +5426,12 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
.unwrap();
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer_task = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
let buffer_b1 = cx_b.executor().spawn(open_buffer_task).await.unwrap();
let (buffer_b1, _lsp) = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
})
.await
.unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
@ -5417,13 +5447,22 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
let buffer_b2;
if rng.gen() {
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
(buffer_b2, _) = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
})
.await
.unwrap();
} else {
buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
(buffer_b2, _) = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
})
.await
.unwrap();
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
}
let buffer_b2 = buffer_b2.await.unwrap();
let definitions = definitions.await.unwrap();
assert_eq!(definitions.len(), 1);
assert_eq!(definitions[0].target.buffer, buffer_b2);

View file

@ -426,8 +426,10 @@ async fn test_ssh_collaboration_formatting_with_prettier(
executor.run_until_parked();
// Opens the buffer and formats it
let buffer_b = project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx))
let (buffer_b, _handle) = project_b
.update(cx_b, |p, cx| {
p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
})
.await
.expect("user B opens buffer for formatting");

View file

@ -129,10 +129,10 @@ use multi_buffer::{
};
use parking_lot::RwLock;
use project::{
lsp_store::{FormatTarget, FormatTrigger},
lsp_store::{FormatTarget, FormatTrigger, OpenLspBufferHandle},
project_settings::{GitGutterSetting, ProjectSettings},
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
Project, ProjectItem, ProjectTransaction, TaskSourceKind,
LspStore, Project, ProjectItem, ProjectTransaction, TaskSourceKind,
};
use rand::prelude::*;
use rpc::{proto::*, ErrorExt};
@ -663,6 +663,7 @@ pub struct Editor {
focused_block: Option<FocusedBlock>,
next_scroll_position: NextScrollCursorCenterTopBottom,
addons: HashMap<TypeId, Box<dyn Addon>>,
registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
_scroll_cursor_center_top_bottom_task: Task<()>,
}
@ -1308,6 +1309,7 @@ impl Editor {
focused_block: None,
next_scroll_position: NextScrollCursorCenterTopBottom::default(),
addons: HashMap::default(),
registered_buffers: HashMap::default(),
_scroll_cursor_center_top_bottom_task: Task::ready(()),
text_style_refinement: None,
};
@ -1325,6 +1327,17 @@ impl Editor {
this.git_blame_inline_enabled = true;
this.start_git_blame_inline(false, cx);
}
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)
});
this.registered_buffers
.insert(buffer.read(cx).remote_id(), handle);
}
}
}
this.report_editor_event("open", None, cx);
@ -1635,6 +1648,22 @@ impl Editor {
self.collapse_matches = collapse_matches;
}
pub fn register_buffers_with_language_servers(&mut self, cx: &mut ViewContext<Self>) {
let buffers = self.buffer.read(cx).all_buffers();
let Some(lsp_store) = self.lsp_store(cx) else {
return;
};
lsp_store.update(cx, |lsp_store, 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)
});
}
})
}
pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
if self.collapse_matches {
return range.start..range.start;
@ -9642,6 +9671,7 @@ impl Editor {
|theme| theme.editor_highlighted_line_background,
cx,
);
editor.register_buffers_with_language_servers(cx);
});
let item = Box::new(editor);
@ -11838,6 +11868,12 @@ impl Editor {
cx.notify();
}
pub fn lsp_store(&self, cx: &AppContext) -> Option<Model<LspStore>> {
self.project
.as_ref()
.map(|project| project.read(cx).lsp_store())
}
fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
cx.notify();
}
@ -11851,6 +11887,7 @@ impl Editor {
match event {
multi_buffer::Event::Edited {
singleton_buffer_edited,
edited_buffer: buffer_edited,
} => {
self.scrollbar_marker_state.dirty = true;
self.active_indent_guides_state.dirty = true;
@ -11859,6 +11896,19 @@ impl Editor {
if self.has_active_inline_completion() {
self.update_visible_inline_completion(cx);
}
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| {
self.registered_buffers.insert(
buffer_id,
lsp_store.register_buffer_with_language_servers(&buffer, cx),
);
})
}
}
}
cx.emit(EditorEvent::BufferEdited);
cx.emit(SearchEvent::MatchesInvalidated);
if *singleton_buffer_edited {
@ -11925,6 +11975,9 @@ impl Editor {
}
multi_buffer::Event::ExcerptsRemoved { ids } => {
self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
let buffer = self.buffer.read(cx);
self.registered_buffers
.retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
}
multi_buffer::Event::ExcerptsEdited { ids } => {

View file

@ -32,9 +32,12 @@ use project::{
project_settings::{LspSettings, ProjectSettings},
};
use serde_json::{self, json};
use std::sync::atomic::{self, AtomicUsize};
use std::{cell::RefCell, future::Future, iter, rc::Rc, time::Instant};
use test::editor_lsp_test_context::rust_lang;
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
use std::{
iter,
sync::atomic::{self, AtomicUsize},
};
use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
use unindent::Unindent;
use util::{
assert_set_eq,
@ -6836,14 +6839,15 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
.await
.unwrap();
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
let (editor, cx) =
cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
let save = editor
.update(cx, |editor, cx| editor.save(true, project.clone(), cx))
.unwrap();
@ -7117,6 +7121,7 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
assert!(!buffer.is_dirty());
assert_eq!(buffer.text(), sample_text_3,)
});
cx.executor().run_until_parked();
cx.executor().start_waiting();
let save = multi_buffer_editor
@ -7188,14 +7193,15 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
.await
.unwrap();
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
let (editor, cx) =
cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
let save = editor
.update(cx, |editor, cx| editor.save(true, project.clone(), cx))
.unwrap();
@ -7339,13 +7345,14 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
.await
.unwrap();
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) =
cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
let format = editor
.update(cx, |editor, cx| {
editor.perform_format(
@ -10332,9 +10339,6 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
})
.await
.unwrap();
cx.executor().run_until_parked();
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
let editor_handle = workspace
.update(cx, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@ -10345,6 +10349,9 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
.downcast::<Editor>()
.unwrap();
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
@ -10434,7 +10441,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let _buffer = project
.update(cx, |project, cx| {
project.open_local_buffer("/a/main.rs", cx)
project.open_local_buffer_with_lsp("/a/main.rs", cx)
})
.await
.unwrap();

File diff suppressed because it is too large Load diff

View file

@ -623,9 +623,9 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
None,
);
let buffer = project
let (buffer, _handle) = project
.update(cx, |project, cx| {
project.open_local_buffer(project_dir.join("test.gleam"), cx)
project.open_local_buffer_with_lsp(project_dir.join("test.gleam"), cx)
})
.await
.unwrap();

View file

@ -57,7 +57,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
let _rust_buffer = 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();

View file

@ -89,6 +89,7 @@ pub enum Event {
},
Edited {
singleton_buffer_edited: bool,
edited_buffer: Option<Model<Buffer>>,
},
TransactionUndone {
transaction_id: TransactionId,
@ -1485,6 +1486,7 @@ impl MultiBuffer {
}]);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
});
cx.emit(Event::ExcerptsAdded {
buffer,
@ -1512,6 +1514,7 @@ impl MultiBuffer {
}]);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
});
cx.emit(Event::ExcerptsRemoved { ids });
cx.notify();
@ -1753,6 +1756,7 @@ impl MultiBuffer {
self.subscriptions.publish_mut(edits);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
});
cx.emit(Event::ExcerptsRemoved { ids });
cx.notify();
@ -1816,6 +1820,7 @@ impl MultiBuffer {
cx.emit(match event {
language::BufferEvent::Edited => Event::Edited {
singleton_buffer_edited: true,
edited_buffer: Some(buffer.clone()),
},
language::BufferEvent::DirtyChanged => Event::DirtyChanged,
language::BufferEvent::Saved => Event::Saved,
@ -1979,6 +1984,7 @@ impl MultiBuffer {
self.subscriptions.publish_mut(edits);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
});
cx.emit(Event::ExcerptsExpanded { ids: vec![id] });
cx.notify();
@ -2076,6 +2082,7 @@ impl MultiBuffer {
self.subscriptions.publish_mut(edits);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
});
cx.emit(Event::ExcerptsExpanded { ids });
cx.notify();
@ -5363,13 +5370,16 @@ mod tests {
events.read().as_slice(),
&[
Event::Edited {
singleton_buffer_edited: false
singleton_buffer_edited: false,
edited_buffer: None,
},
Event::Edited {
singleton_buffer_edited: false
singleton_buffer_edited: false,
edited_buffer: None,
},
Event::Edited {
singleton_buffer_edited: false
singleton_buffer_edited: false,
edited_buffer: None,
}
]
);

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();

View file

@ -292,7 +292,7 @@ mod tests {
let _buffer = project
.update(cx, |project, cx| {
project.open_local_buffer("/dir/test.rs", cx)
project.open_local_buffer_with_lsp("/dir/test.rs", cx)
})
.await
.unwrap();

View file

@ -304,7 +304,9 @@ message Envelope {
InstallExtension install_extension = 287;
GetStagedText get_staged_text = 288;
GetStagedTextResponse get_staged_text_response = 289; // current max
GetStagedTextResponse get_staged_text_response = 289;
RegisterBufferWithLanguageServers register_buffer_with_language_servers = 290;
}
reserved 87 to 88;
@ -2537,7 +2539,6 @@ message UpdateGitBranch {
string branch_name = 2;
ProjectPath repository = 3;
}
message GetPanicFiles {
}
@ -2582,3 +2583,8 @@ message InstallExtension {
Extension extension = 1;
string tmp_dir = 2;
}
message RegisterBufferWithLanguageServers{
uint64 project_id = 1;
uint64 buffer_id = 2;
}

View file

@ -373,6 +373,7 @@ messages!(
(SyncExtensions, Background),
(SyncExtensionsResponse, Background),
(InstallExtension, Background),
(RegisterBufferWithLanguageServers, Background),
);
request_messages!(
@ -499,6 +500,7 @@ request_messages!(
(CancelLanguageServerWork, Ack),
(SyncExtensions, SyncExtensionsResponse),
(InstallExtension, Ack),
(RegisterBufferWithLanguageServers, Ack),
);
entity_messages!(
@ -584,6 +586,7 @@ entity_messages!(
ActiveToolchain,
GetPathMetadata,
CancelLanguageServerWork,
RegisterBufferWithLanguageServers,
);
entity_messages!(

View file

@ -440,9 +440,9 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
// Wait for the settings to synchronize
cx.run_until_parked();
let buffer = project
let (buffer, _handle) = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
project.open_buffer_with_lsp((worktree_id, Path::new("src/lib.rs")), cx)
})
.await
.unwrap();
@ -616,9 +616,9 @@ async fn test_remote_cancel_language_server_work(
cx.run_until_parked();
let buffer = project
let (buffer, _handle) = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
project.open_buffer_with_lsp((worktree_id, Path::new("src/lib.rs")), cx)
})
.await
.unwrap();