From 6731d92f6027d6697f7f833d917e6987a1d16ec8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 8 Feb 2022 15:48:44 -0800 Subject: [PATCH] Give the editor a handle to the project, not a weak handle to the workspace Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/Cargo.toml | 2 + crates/editor/src/editor.rs | 105 +++++++++++++------------- crates/editor/src/items.rs | 2 +- crates/language/src/language.rs | 13 ++-- crates/language/src/tests.rs | 2 +- crates/lsp/src/lsp.rs | 19 +++-- crates/project/Cargo.toml | 6 +- crates/project/src/project.rs | 61 ++++++--------- crates/server/src/rpc.rs | 12 +-- 10 files changed, 112 insertions(+), 112 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index b12d77426b..8342d742d6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -148,7 +148,7 @@ impl ProjectDiagnosticsEditor { let mut editor = Editor::for_buffer( excerpts.clone(), build_settings.clone(), - Some(workspace.clone()), + Some(project.clone()), cx, ); editor.set_vertical_scroll_margin(5, cx); diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 77dcbab3df..23ce651d9b 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -12,6 +12,7 @@ test-support = [ "text/test-support", "language/test-support", "gpui/test-support", + "project/test-support", "util/test-support", ] @@ -48,6 +49,7 @@ language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } +project = { path = "../project", features = ["test-support"] } ctor = "0.1" env_logger = "0.8" rand = "0.8" diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 12f7a145a4..cec5d399e0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -40,6 +40,7 @@ pub use multi_buffer::{ }; use ordered_float::OrderedFloat; use postage::watch; +use project::Project; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use smol::Timer; @@ -415,7 +416,7 @@ pub struct Editor { scroll_top_anchor: Option, autoscroll_request: Option, build_settings: BuildSettings, - workspace: Option>, + project: Option>, focused: bool, show_local_cursors: bool, blink_epoch: usize, @@ -772,17 +773,17 @@ impl Editor { pub fn for_buffer( buffer: ModelHandle, build_settings: BuildSettings, - workspace: Option>, + project: Option>, cx: &mut ViewContext, ) -> Self { - Self::new(buffer, build_settings, workspace, cx) + Self::new(buffer, build_settings, project, cx) } pub fn clone(&self, cx: &mut ViewContext) -> Self { let mut clone = Self::new( self.buffer.clone(), self.build_settings.clone(), - self.workspace.clone(), + self.project.clone(), cx, ); clone.scroll_position = self.scroll_position; @@ -797,7 +798,7 @@ impl Editor { pub fn new( buffer: ModelHandle, build_settings: BuildSettings, - workspace: Option>, + project: Option>, cx: &mut ViewContext, ) -> Self { let settings = build_settings(cx); @@ -832,7 +833,7 @@ impl Editor { select_larger_syntax_node_stack: Vec::new(), active_diagnostics: None, build_settings, - workspace, + project, scroll_position: Vector2F::zero(), scroll_top_anchor: None, autoscroll_request: None, @@ -1882,8 +1883,8 @@ impl Editor { } fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext) { - let project = if let Some(workspace) = self.workspace.as_ref().and_then(|w| w.upgrade(cx)) { - workspace.read(cx).project().clone() + let project = if let Some(project) = self.project.clone() { + project } else { return; }; @@ -2050,13 +2051,7 @@ impl Editor { } self.end_transaction(cx); - let project = self - .workspace - .as_ref()? - .upgrade(cx)? - .read(cx) - .project() - .clone(); + let project = self.project.clone()?; let apply_edits = project.update(cx, |project, cx| { project.apply_additional_edits_for_completion( buffer_handle, @@ -2077,19 +2072,14 @@ impl Editor { } else { return; }; - let workspace = if let Some(workspace) = self.workspace.as_ref().and_then(|w| w.upgrade(cx)) - { - workspace + let project = if let Some(project) = self.project.clone() { + project } else { return; }; let (buffer, head) = self.buffer.read(cx).text_anchor_for_position(head, cx); - let actions = workspace - .read(cx) - .project() - .clone() - .update(cx, |project, cx| project.code_actions(&buffer, head, cx)); + let actions = project.update(cx, |project, cx| project.code_actions(&buffer, head, cx)); cx.spawn(|this, mut cx| async move { let actions = actions.await?; @@ -2114,13 +2104,14 @@ impl Editor { } fn confirm_code_action( - &mut self, + workspace: &mut Workspace, ConfirmCodeAction(action_ix): &ConfirmCodeAction, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let workspace = self.workspace.as_ref()?.upgrade(cx)?; - - let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? { + let editor = workspace.active_item(cx)?.act_as::(cx)?; + let actions_menu = if let ContextMenu::CodeActions(menu) = + editor.update(cx, |editor, cx| editor.hide_context_menu(cx))? + { menu } else { return None; @@ -2129,14 +2120,10 @@ impl Editor { let action = actions_menu.actions.get(action_ix)?.clone(); let buffer = actions_menu.buffer; - let apply_code_actions = workspace - .read(cx) - .project() - .clone() - .update(cx, |project, cx| { - project.apply_code_action(buffer, action, true, cx) - }); - Some(cx.spawn(|_, mut cx| async move { + let apply_code_actions = workspace.project().clone().update(cx, |project, cx| { + project.apply_code_action(buffer, action, true, cx) + }); + Some(cx.spawn(|workspace, mut cx| async move { let project_transaction = apply_code_actions.await?; // TODO: replace this with opening a single tab that is a multibuffer @@ -5272,9 +5259,10 @@ fn styled_runs_for_completion_label<'a>( #[cfg(test)] mod tests { use super::*; - use language::{FakeFile, LanguageConfig}; + use language::LanguageConfig; use lsp::FakeLanguageServer; - use std::{cell::RefCell, path::Path, rc::Rc, time::Instant}; + use project::{FakeFs, ProjectPath}; + use std::{cell::RefCell, rc::Rc, time::Instant}; use text::Point; use unindent::Unindent; use util::test::sample_text; @@ -7562,7 +7550,7 @@ mod tests { }), ..Default::default() }, - cx.background(), + &cx, ) .await; @@ -7573,30 +7561,43 @@ mod tests { " .unindent(); - let buffer = cx.add_model(|cx| { - Buffer::from_file( - 0, - text, - Box::new(FakeFile { - path: Arc::from(Path::new("/the/file")), - }), - cx, - ) - .with_language_server(language_server, cx) + let fs = Arc::new(FakeFs::new(cx.background().clone())); + fs.insert_file("/file", text).await.unwrap(); + + let project = Project::test(fs, &mut cx); + + let (worktree, relative_path) = project + .update(&mut cx, |project, cx| { + project.find_or_create_local_worktree("/file", false, cx) + }) + .await + .unwrap(); + let project_path = ProjectPath { + worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()), + path: relative_path.into(), + }; + let buffer = project + .update(&mut cx, |project, cx| project.open_buffer(project_path, cx)) + .await + .unwrap(); + buffer.update(&mut cx, |buffer, cx| { + buffer.set_language_server(Some(language_server), cx); }); + let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); buffer.next_notification(&cx).await; let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx)); editor.update(&mut cx, |editor, cx| { + editor.project = Some(project); editor.select_ranges([Point::new(0, 3)..Point::new(0, 3)], None, cx); editor.handle_input(&Input(".".to_string()), cx); }); handle_completion_request( &mut fake, - "/the/file", + "/file", Point::new(0, 4), &[ (Point::new(0, 4)..Point::new(0, 4), "first_completion"), @@ -7658,7 +7659,7 @@ mod tests { handle_completion_request( &mut fake, - "/the/file", + "/file", Point::new(2, 7), &[ (Point::new(2, 6)..Point::new(2, 7), "fourth_completion"), @@ -7677,7 +7678,7 @@ mod tests { handle_completion_request( &mut fake, - "/the/file", + "/file", Point::new(2, 8), &[ (Point::new(2, 6)..Point::new(2, 8), "fourth_completion"), diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index d9c7e180f5..0aeb32dbd0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -55,7 +55,7 @@ impl ItemHandle for BufferItemHandle { let mut editor = Editor::for_buffer( buffer, crate::settings_builder(weak_buffer, workspace.settings()), - Some(workspace.weak_handle()), + Some(workspace.project().clone()), cx, ); editor.nav_history = Some(ItemNavHistory::new(nav_history, &cx.handle())); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 3c6d6f0f42..4652da8bff 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -8,7 +8,7 @@ mod tests; use anyhow::{anyhow, Result}; use collections::HashSet; -use gpui::AppContext; +use gpui::{AppContext, TestAppContext}; use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::Mutex; @@ -357,18 +357,15 @@ impl CompletionLabel { #[cfg(any(test, feature = "test-support"))] impl LanguageServerConfig { - pub async fn fake( - executor: Arc, - ) -> (Self, lsp::FakeLanguageServer) { - Self::fake_with_capabilities(Default::default(), executor).await + pub async fn fake(cx: &TestAppContext) -> (Self, lsp::FakeLanguageServer) { + Self::fake_with_capabilities(Default::default(), cx).await } pub async fn fake_with_capabilities( capabilites: lsp::ServerCapabilities, - executor: Arc, + cx: &TestAppContext, ) -> (Self, lsp::FakeLanguageServer) { - let (server, fake) = - lsp::LanguageServer::fake_with_capabilities(capabilites, executor).await; + let (server, fake) = lsp::LanguageServer::fake_with_capabilities(capabilites, cx).await; fake.started .store(false, std::sync::atomic::Ordering::SeqCst); let started = fake.started.clone(); diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 1cede148de..6e2ebd9015 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -557,7 +557,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte #[gpui::test] async fn test_diagnostics(mut cx: gpui::TestAppContext) { - let (language_server, mut fake) = lsp::LanguageServer::fake(cx.background()).await; + let (language_server, mut fake) = lsp::LanguageServer::fake(&cx).await; let mut rust_lang = rust_lang(); rust_lang.config.language_server = Some(LanguageServerConfig { disk_based_diagnostic_sources: HashSet::from_iter(["disk".to_string()]), diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index e9f9afd3fb..6a926c7e2a 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Context, Result}; use futures::{io::BufWriter, AsyncRead, AsyncWrite}; -use gpui::{executor, Task}; +use gpui::{executor, Task, TestAppContext}; use parking_lot::{Mutex, RwLock}; use postage::{barrier, oneshot, prelude::Stream, sink::Sink, watch}; use serde::{Deserialize, Serialize}; @@ -14,6 +14,7 @@ use std::{ collections::HashMap, future::Future, io::Write, + rc::Rc, str::FromStr, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, @@ -472,6 +473,7 @@ pub struct FakeLanguageServer { buffer: Vec, stdin: smol::io::BufReader, stdout: smol::io::BufWriter, + executor: Rc, pub started: Arc, } @@ -483,13 +485,13 @@ pub struct RequestId { #[cfg(any(test, feature = "test-support"))] impl LanguageServer { - pub async fn fake(executor: Arc) -> (Arc, FakeLanguageServer) { - Self::fake_with_capabilities(Default::default(), executor).await + pub async fn fake(cx: &TestAppContext) -> (Arc, FakeLanguageServer) { + Self::fake_with_capabilities(Default::default(), cx).await } pub async fn fake_with_capabilities( capabilities: ServerCapabilities, - executor: Arc, + cx: &TestAppContext, ) -> (Arc, FakeLanguageServer) { let stdin = async_pipe::pipe(); let stdout = async_pipe::pipe(); @@ -497,10 +499,12 @@ impl LanguageServer { stdin: smol::io::BufReader::new(stdin.1), stdout: smol::io::BufWriter::new(stdout.0), buffer: Vec::new(), + executor: cx.foreground(), started: Arc::new(std::sync::atomic::AtomicBool::new(true)), }; - let server = Self::new_internal(stdin.0, stdout.1, Path::new("/"), executor).unwrap(); + let server = + Self::new_internal(stdin.0, stdout.1, Path::new("/"), cx.background()).unwrap(); let (init_id, _) = fake.receive_request::().await; fake.respond( @@ -549,11 +553,14 @@ impl FakeLanguageServer { } pub async fn receive_request(&mut self) -> (RequestId, T::Params) { + let executor = self.executor.clone(); + executor.start_waiting(); loop { self.receive().await; if let Ok(request) = serde_json::from_slice::>(&self.buffer) { assert_eq!(request.method, T::METHOD); assert_eq!(request.jsonrpc, JSON_RPC_VERSION); + executor.finish_waiting(); return ( RequestId { id: request.id, @@ -704,7 +711,7 @@ mod tests { async fn test_fake(cx: TestAppContext) { SimpleLogger::init(log::LevelFilter::Info, Default::default()).unwrap(); - let (server, mut fake) = LanguageServer::fake(cx.background()).await; + let (server, mut fake) = LanguageServer::fake(&cx).await; let (message_tx, message_rx) = channel::unbounded(); let (diagnostics_tx, diagnostics_rx) = channel::unbounded(); diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index d302be874f..3cc9a45923 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -7,7 +7,11 @@ edition = "2021" path = "src/project.rs" [features] -test-support = ["language/test-support", "text/test-support"] +test-support = [ + "client/test-support", + "language/test-support", + "text/test-support", +] [dependencies] text = { path = "../text" } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2d15275f22..a359885ba7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -322,6 +322,15 @@ impl Project { })) } + #[cfg(any(test, feature = "test-support"))] + pub fn test(fs: Arc, cx: &mut gpui::TestAppContext) -> ModelHandle { + let languages = Arc::new(LanguageRegistry::new()); + let http_client = client::test::FakeHttpClient::with_404_response(); + let client = client::Client::new(http_client.clone()); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + cx.update(|cx| Project::local(client, user_store, languages, fs, cx)) + } + fn set_remote_id(&mut self, remote_id: Option, cx: &mut ModelContext) { if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state { *remote_id_tx.borrow_mut() = remote_id; @@ -1197,21 +1206,11 @@ impl Project { if worktree.read(cx).as_local().is_some() { let buffer_abs_path = buffer_abs_path.unwrap(); - let lang_name; - let lang_server; - if let Some(lang) = &language { - lang_name = lang.name().to_string(); - if let Some(server) = self - .language_servers - .get(&(worktree.read(cx).id(), lang_name.clone())) - { - lang_server = server.clone(); - } else { - return Task::ready(Err(anyhow!("buffer does not have a language server"))); - }; + let lang_server = if let Some(server) = source_buffer.language_server().cloned() { + server } else { - return Task::ready(Err(anyhow!("buffer does not have a language"))); - } + return Task::ready(Err(anyhow!("buffer does not have a language server"))); + }; cx.spawn(|_, cx| async move { let completions = lang_server @@ -2793,7 +2792,7 @@ mod tests { use client::test::FakeHttpClient; use fs::RealFs; use futures::StreamExt; - use gpui::{test::subscribe, TestAppContext}; + use gpui::test::subscribe; use language::{ tree_sitter_rust, AnchorRangeExt, Diagnostic, LanguageConfig, LanguageRegistry, LanguageServerConfig, Point, @@ -2830,7 +2829,7 @@ mod tests { ) .unwrap(); - let project = build_project(Arc::new(RealFs), &mut cx); + let project = Project::test(Arc::new(RealFs), &mut cx); let (tree, _) = project .update(&mut cx, |project, cx| { @@ -2870,8 +2869,7 @@ mod tests { #[gpui::test] async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) { - let (language_server_config, mut fake_server) = - LanguageServerConfig::fake(cx.background()).await; + let (language_server_config, mut fake_server) = LanguageServerConfig::fake(&cx).await; let progress_token = language_server_config .disk_based_diagnostics_progress_token .clone() @@ -3012,7 +3010,7 @@ mod tests { } })); - let project = build_project(Arc::new(RealFs), &mut cx); + let project = Project::test(Arc::new(RealFs), &mut cx); let (tree, _) = project .update(&mut cx, |project, cx| { project.find_or_create_local_worktree(&dir.path(), false, cx) @@ -3035,8 +3033,7 @@ mod tests { #[gpui::test] async fn test_definition(mut cx: gpui::TestAppContext) { - let (language_server_config, mut fake_server) = - LanguageServerConfig::fake(cx.background()).await; + let (language_server_config, mut fake_server) = LanguageServerConfig::fake(&cx).await; let mut languages = LanguageRegistry::new(); languages.add(Arc::new(Language::new( @@ -3169,7 +3166,7 @@ mod tests { ) .await; - let project = build_project(fs.clone(), &mut cx); + let project = Project::test(fs.clone(), &mut cx); let worktree_id = project .update(&mut cx, |p, cx| { p.find_or_create_local_worktree("/dir", false, cx) @@ -3207,7 +3204,7 @@ mod tests { ) .await; - let project = build_project(fs.clone(), &mut cx); + let project = Project::test(fs.clone(), &mut cx); let worktree_id = project .update(&mut cx, |p, cx| { p.find_or_create_local_worktree("/dir/file1", false, cx) @@ -3249,7 +3246,7 @@ mod tests { } })); - let project = build_project(Arc::new(RealFs), &mut cx); + let project = Project::test(Arc::new(RealFs), &mut cx); let rpc = project.read_with(&cx, |p, _| p.client.clone()); let (tree, _) = project @@ -3395,7 +3392,7 @@ mod tests { ) .await; - let project = build_project(fs.clone(), &mut cx); + let project = Project::test(fs.clone(), &mut cx); let worktree_id = project .update(&mut cx, |p, cx| { p.find_or_create_local_worktree("/the-dir", false, cx) @@ -3445,7 +3442,7 @@ mod tests { "file3": "ghi", })); - let project = build_project(Arc::new(RealFs), &mut cx); + let project = Project::test(Arc::new(RealFs), &mut cx); let (worktree, _) = project .update(&mut cx, |p, cx| { p.find_or_create_local_worktree(dir.path(), false, cx) @@ -3579,7 +3576,7 @@ mod tests { let initial_contents = "aaa\nbbbbb\nc\n"; let dir = temp_tree(json!({ "the-file": initial_contents })); - let project = build_project(Arc::new(RealFs), &mut cx); + let project = Project::test(Arc::new(RealFs), &mut cx); let (worktree, _) = project .update(&mut cx, |p, cx| { p.find_or_create_local_worktree(dir.path(), false, cx) @@ -3690,7 +3687,7 @@ mod tests { ) .await; - let project = build_project(fs.clone(), &mut cx); + let project = Project::test(fs.clone(), &mut cx); let (worktree, _) = project .update(&mut cx, |p, cx| { p.find_or_create_local_worktree("/the-dir", false, cx) @@ -3930,12 +3927,4 @@ mod tests { ] ); } - - fn build_project(fs: Arc, cx: &mut TestAppContext) -> ModelHandle { - let languages = Arc::new(LanguageRegistry::new()); - let http_client = FakeHttpClient::with_404_response(); - let client = client::Client::new(http_client.clone()); - let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - cx.update(|cx| Project::local(client, user_store, languages, fs, cx)) - } } diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 76b50139fc..677d1e979f 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -2090,7 +2090,7 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_server) = - LanguageServerConfig::fake(cx_a.background()).await; + LanguageServerConfig::fake(&cx_a).await; Arc::get_mut(&mut lang_registry) .unwrap() .add(Arc::new(Language::new( @@ -2322,7 +2322,7 @@ mod tests { }), ..Default::default() }, - cx_a.background(), + &cx_a, ) .await; Arc::get_mut(&mut lang_registry) @@ -2401,7 +2401,7 @@ mod tests { Editor::for_buffer( cx.add_model(|cx| MultiBuffer::singleton(buffer_b.clone(), cx)), Arc::new(|cx| EditorSettings::test(cx)), - None, + Some(project_b.clone()), cx, ) }); @@ -2537,7 +2537,7 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_server) = - LanguageServerConfig::fake(cx_a.background()).await; + LanguageServerConfig::fake(&cx_a).await; Arc::get_mut(&mut lang_registry) .unwrap() .add(Arc::new(Language::new( @@ -2656,7 +2656,7 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_server) = - LanguageServerConfig::fake(cx_a.background()).await; + LanguageServerConfig::fake(&cx_a).await; Arc::get_mut(&mut lang_registry) .unwrap() .add(Arc::new(Language::new( @@ -2811,7 +2811,7 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_server) = - LanguageServerConfig::fake(cx_a.background()).await; + LanguageServerConfig::fake(&cx_a).await; Arc::get_mut(&mut lang_registry) .unwrap() .add(Arc::new(Language::new(