From 728c7081509740ecb02a6e24e625da53715afd67 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 16 Mar 2022 17:40:09 -0600 Subject: [PATCH] WIP: Massage opening of editors Co-Authored-By: Max Brunsfeld --- crates/diagnostics/src/diagnostics.rs | 8 +- crates/editor/src/editor.rs | 9 +- crates/editor/src/items.rs | 8 +- crates/project/src/project.rs | 103 +++++++++----- crates/search/src/project_search.rs | 4 - crates/server/src/rpc.rs | 102 ++++++++++---- crates/workspace/src/pane.rs | 48 +++++-- crates/workspace/src/workspace.rs | 190 ++++++++++++++++++++------ 8 files changed, 338 insertions(+), 134 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 2b10adb202283d0b47c18506aaf3559faba06901..7c69f85acf86eed7874732129fa01cf16eb19b7a 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -155,7 +155,9 @@ impl ProjectDiagnosticsEditor { async move { for path in paths { let buffer = project - .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx)) + .update(&mut cx, |project, cx| { + project.open_buffer_for_path(path.clone(), cx) + }) .await?; this.update(&mut cx, |view, cx| view.populate_excerpts(path, buffer, cx)) } @@ -449,10 +451,6 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { None } - fn project_entry_id(&self, _: &AppContext) -> Option { - None - } - fn navigate(&mut self, data: Box, cx: &mut ViewContext) { self.editor .update(cx, |editor, cx| editor.navigate(data, cx)); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 704a2cf248156821da6f194428018c3b48075f07..39dad90ece4e572800130bf1b24abf59aa0f1bbf 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -846,10 +846,7 @@ impl Editor { .and_then(|file| file.project_entry_id(cx)) { return workspace - .open_item_for_project_entry(project_entry, cx, |cx| { - let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - Editor::for_buffer(multibuffer, Some(project.clone()), cx) - }) + .open_editor(project_entry, cx) .downcast::() .unwrap(); } @@ -8442,7 +8439,9 @@ mod tests { .0 .read_with(cx, |tree, _| tree.id()); let buffer = project - .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx)) + .update(cx, |project, cx| { + project.open_buffer_for_path((worktree_id, ""), cx) + }) .await .unwrap(); let mut fake_server = fake_servers.next().await.unwrap(); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index a9bac1908b5e5ce90eeec4ad0d9f976999a31227..6fec3d7a1cd967bd658fe5c18af5bd8ecc95436f 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -5,7 +5,7 @@ use gpui::{ View, ViewContext, ViewHandle, WeakModelHandle, }; use language::{Bias, Buffer, Diagnostic, File as _}; -use project::{File, Project, ProjectEntryId, ProjectPath}; +use project::{File, Project, ProjectPath}; use std::fmt::Write; use std::path::PathBuf; use text::{Point, Selection}; @@ -34,7 +34,7 @@ impl PathOpener for BufferOpener { window_id: usize, cx: &mut ModelContext, ) -> Option>>> { - let buffer = project.open_buffer(project_path, cx); + let buffer = project.open_buffer_for_path(project_path, cx); Some(cx.spawn(|project, mut cx| async move { let buffer = buffer.await?; let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); @@ -75,10 +75,6 @@ impl ItemView for Editor { }) } - fn project_entry_id(&self, cx: &AppContext) -> Option { - File::from_dyn(self.buffer().read(cx).file(cx)).and_then(|file| file.project_entry_id(cx)) - } - fn clone_on_split(&self, cx: &mut ViewContext) -> Option where Self: Sized, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ae60ab825fac9bce7de85a011f98781bfabae937..3484796dd9c35da31a29e8894468085dbceb481d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -818,7 +818,19 @@ impl Project { Ok(buffer) } - pub fn open_buffer( + pub fn open_buffer_for_entry( + &mut self, + entry_id: ProjectEntryId, + cx: &mut ModelContext, + ) -> Task>> { + if let Some(project_path) = self.path_for_entry(entry_id, cx) { + self.open_buffer_for_path(project_path, cx) + } else { + Task::ready(Err(anyhow!("entry not found"))) + } + } + + pub fn open_buffer_for_path( &mut self, path: impl Into, cx: &mut ModelContext, @@ -953,8 +965,10 @@ impl Project { worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()), path: relative_path.into(), }; - this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx)) - .await + this.update(&mut cx, |this, cx| { + this.open_buffer_for_path(project_path, cx) + }) + .await }) } @@ -2854,7 +2868,9 @@ impl Project { let buffers_tx = buffers_tx.clone(); cx.spawn(|mut cx| async move { if let Some(buffer) = this - .update(&mut cx, |this, cx| this.open_buffer(project_path, cx)) + .update(&mut cx, |this, cx| { + this.open_buffer_for_path(project_path, cx) + }) .await .log_err() { @@ -3258,6 +3274,14 @@ impl Project { .map(|entry| entry.id) } + pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option { + let worktree = self.worktree_for_entry(entry_id, cx)?; + let worktree = worktree.read(cx); + let worktree_id = worktree.id(); + let path = worktree.entry_for_id(entry_id)?.path.clone(); + Some(ProjectPath { worktree_id, path }) + } + // RPC message handlers async fn handle_unshare_project( @@ -3867,7 +3891,7 @@ impl Project { let peer_id = envelope.original_sender_id()?; let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let open_buffer = this.update(&mut cx, |this, cx| { - this.open_buffer( + this.open_buffer_for_path( ProjectPath { worktree_id, path: PathBuf::from(envelope.payload.path).into(), @@ -4664,7 +4688,7 @@ mod tests { // Open a buffer without an associated language server. let toml_buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "Cargo.toml"), cx) + project.open_buffer_for_path((worktree_id, "Cargo.toml"), cx) }) .await .unwrap(); @@ -4672,7 +4696,7 @@ mod tests { // Open a buffer with an associated language server. let rust_buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "test.rs"), cx) + project.open_buffer_for_path((worktree_id, "test.rs"), cx) }) .await .unwrap(); @@ -4719,7 +4743,7 @@ mod tests { // Open a third buffer with a different associated language server. let json_buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "package.json"), cx) + project.open_buffer_for_path((worktree_id, "package.json"), cx) }) .await .unwrap(); @@ -4750,7 +4774,7 @@ mod tests { // it is also configured based on the existing language server's capabilities. let rust_buffer2 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "test2.rs"), cx) + project.open_buffer_for_path((worktree_id, "test2.rs"), cx) }) .await .unwrap(); @@ -4861,7 +4885,7 @@ mod tests { // Cause worktree to start the fake language server let _buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, Path::new("b.rs")), cx) + project.open_buffer_for_path((worktree_id, Path::new("b.rs")), cx) }) .await .unwrap(); @@ -4908,7 +4932,9 @@ mod tests { ); let buffer = project - .update(cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)) + .update(cx, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.rs"), cx) + }) .await .unwrap(); @@ -4975,7 +5001,7 @@ mod tests { let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "a.rs"), cx) + project.open_buffer_for_path((worktree_id, "a.rs"), cx) }) .await .unwrap(); @@ -5251,7 +5277,7 @@ mod tests { let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "a.rs"), cx) + project.open_buffer_for_path((worktree_id, "a.rs"), cx) }) .await .unwrap(); @@ -5356,7 +5382,7 @@ mod tests { let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "a.rs"), cx) + project.open_buffer_for_path((worktree_id, "a.rs"), cx) }) .await .unwrap(); @@ -5514,7 +5540,7 @@ mod tests { let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "a.rs"), cx) + project.open_buffer_for_path((worktree_id, "a.rs"), cx) }) .await .unwrap(); @@ -5697,7 +5723,7 @@ mod tests { let buffer = project .update(cx, |project, cx| { - project.open_buffer( + project.open_buffer_for_path( ProjectPath { worktree_id, path: Path::new("").into(), @@ -5793,7 +5819,9 @@ mod tests { .read_with(cx, |tree, _| tree.id()); let buffer = project - .update(cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx)) + .update(cx, |p, cx| { + p.open_buffer_for_path((worktree_id, "file1"), cx) + }) .await .unwrap(); buffer @@ -5831,7 +5859,7 @@ mod tests { .read_with(cx, |tree, _| tree.id()); let buffer = project - .update(cx, |p, cx| p.open_buffer((worktree_id, ""), cx)) + .update(cx, |p, cx| p.open_buffer_for_path((worktree_id, ""), cx)) .await .unwrap(); buffer @@ -5881,7 +5909,7 @@ mod tests { let opened_buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "file1"), cx) + project.open_buffer_for_path((worktree_id, "file1"), cx) }) .await .unwrap(); @@ -5916,7 +5944,8 @@ mod tests { let worktree_id = tree.read_with(cx, |tree, _| tree.id()); let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| { - let buffer = project.update(cx, |p, cx| p.open_buffer((worktree_id, path), cx)); + let buffer = + project.update(cx, |p, cx| p.open_buffer_for_path((worktree_id, path), cx)); async move { buffer.await.unwrap() } }; let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| { @@ -6065,9 +6094,9 @@ mod tests { // Spawn multiple tasks to open paths, repeating some paths. let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| { ( - p.open_buffer((worktree_id, "a.txt"), cx), - p.open_buffer((worktree_id, "b.txt"), cx), - p.open_buffer((worktree_id, "a.txt"), cx), + p.open_buffer_for_path((worktree_id, "a.txt"), cx), + p.open_buffer_for_path((worktree_id, "b.txt"), cx), + p.open_buffer_for_path((worktree_id, "a.txt"), cx), ) }); @@ -6084,7 +6113,9 @@ mod tests { // Open the same path again while it is still open. drop(buffer_a_1); let buffer_a_3 = project - .update(cx, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.txt"), cx) + }) .await .unwrap(); @@ -6117,7 +6148,9 @@ mod tests { .await; let buffer1 = project - .update(cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx)) + .update(cx, |p, cx| { + p.open_buffer_for_path((worktree_id, "file1"), cx) + }) .await .unwrap(); let events = Rc::new(RefCell::new(Vec::new())); @@ -6187,7 +6220,9 @@ mod tests { // When a file is deleted, the buffer is considered dirty. let events = Rc::new(RefCell::new(Vec::new())); let buffer2 = project - .update(cx, |p, cx| p.open_buffer((worktree_id, "file2"), cx)) + .update(cx, |p, cx| { + p.open_buffer_for_path((worktree_id, "file2"), cx) + }) .await .unwrap(); buffer2.update(cx, |_, cx| { @@ -6208,7 +6243,9 @@ mod tests { // When a file is already dirty when deleted, we don't emit a Dirtied event. let events = Rc::new(RefCell::new(Vec::new())); let buffer3 = project - .update(cx, |p, cx| p.open_buffer((worktree_id, "file3"), cx)) + .update(cx, |p, cx| { + p.open_buffer_for_path((worktree_id, "file3"), cx) + }) .await .unwrap(); buffer3.update(cx, |_, cx| { @@ -6254,7 +6291,9 @@ mod tests { let abs_path = dir.path().join("the-file"); let buffer = project - .update(cx, |p, cx| p.open_buffer((worktree_id, "the-file"), cx)) + .update(cx, |p, cx| { + p.open_buffer_for_path((worktree_id, "the-file"), cx) + }) .await .unwrap(); @@ -6360,7 +6399,9 @@ mod tests { let worktree_id = worktree.read_with(cx, |tree, _| tree.id()); let buffer = project - .update(cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)) + .update(cx, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.rs"), cx) + }) .await .unwrap(); @@ -6633,7 +6674,7 @@ mod tests { let buffer = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, Path::new("one.rs")), cx) + project.open_buffer_for_path((worktree_id, Path::new("one.rs")), cx) }) .await .unwrap(); @@ -6771,7 +6812,7 @@ mod tests { let buffer_4 = project .update(cx, |project, cx| { - project.open_buffer((worktree_id, "four.rs"), cx) + project.open_buffer_for_path((worktree_id, "four.rs"), cx) }) .await .unwrap(); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 20df486f486a0fcf58cd6853e55d53262e7a62da..6208ff4aaa221442d754a539a5c2ab1571664dc6 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -250,10 +250,6 @@ impl ItemView for ProjectSearchView { None } - fn project_entry_id(&self, _: &AppContext) -> Option { - None - } - fn can_save(&self, _: &gpui::AppContext) -> bool { true } diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index ed45c2d5d6b80d46c9d0a7455ff3621506bf14c1..f891dca71cc12a6d30cfeebba6707a48fda476da 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1137,7 +1137,9 @@ mod tests { // Open the same file as client B and client A. let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "b.txt"), cx) + }) .await .unwrap(); let buffer_b = cx_b.add_model(|cx| MultiBuffer::singleton(buffer_b, cx)); @@ -1148,7 +1150,9 @@ mod tests { assert!(project.has_open_buffer((worktree_id, "b.txt"), cx)) }); let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer_for_path((worktree_id, "b.txt"), cx) + }) .await .unwrap(); @@ -1238,7 +1242,9 @@ mod tests { .await .unwrap(); project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.txt"), cx) + }) .await .unwrap(); @@ -1273,7 +1279,9 @@ mod tests { .await .unwrap(); project_b2 - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.txt"), cx) + }) .await .unwrap(); } @@ -1352,11 +1360,15 @@ mod tests { // Open and edit a buffer as both guests B and C. let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "file1"), cx) + }) .await .unwrap(); let buffer_c = project_c - .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1"), cx)) + .update(cx_c, |p, cx| { + p.open_buffer_for_path((worktree_id, "file1"), cx) + }) .await .unwrap(); buffer_b.update(cx_b, |buf, cx| buf.edit([0..0], "i-am-b, ", cx)); @@ -1364,7 +1376,9 @@ mod tests { // Open and edit that buffer as the host. let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer_for_path((worktree_id, "file1"), cx) + }) .await .unwrap(); @@ -1514,7 +1528,9 @@ mod tests { // Open a buffer as client B let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.txt"), cx) + }) .await .unwrap(); @@ -1597,7 +1613,9 @@ mod tests { // Open a buffer as client B let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.txt"), cx) + }) .await .unwrap(); buffer_b.read_with(cx_b, |buf, _| { @@ -1677,14 +1695,16 @@ mod tests { // Open a buffer as client A let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_a, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.txt"), cx) + }) .await .unwrap(); // Start opening the same buffer as client B - let buffer_b = cx_b - .background() - .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))); + let buffer_b = cx_b.background().spawn(project_b.update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.txt"), cx) + })); // Edit the buffer as client A while client B is still opening it. cx_b.background().simulate_random_delay().await; @@ -1760,9 +1780,9 @@ mod tests { .await; // Begin opening a buffer as client B, but leave the project before the open completes. - let buffer_b = cx_b - .background() - .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))); + let buffer_b = cx_b.background().spawn(project_b.update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.txt"), cx) + })); cx_b.update(|_| drop(project_b)); drop(buffer_b); @@ -1932,7 +1952,7 @@ mod tests { let _ = cx_a .background() .spawn(project_a.update(cx_a, |project, cx| { - project.open_buffer( + project.open_buffer_for_path( ProjectPath { worktree_id, path: Path::new("other.rs").into(), @@ -2053,7 +2073,9 @@ mod tests { // Open the file with the errors on client B. They should be present. let buffer_b = cx_b .background() - .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .spawn(project_b.update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.rs"), cx) + })) .await .unwrap(); @@ -2171,7 +2193,9 @@ mod tests { // Open a file in an editor as the guest. let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "main.rs"), cx) + }) .await .unwrap(); let (window_b, _) = cx_b.add_window(|_| EmptyView); @@ -2245,7 +2269,9 @@ mod tests { // Open the buffer on the host. 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_buffer_for_path((worktree_id, "main.rs"), cx) + }) .await .unwrap(); buffer_a @@ -2369,7 +2395,9 @@ mod tests { let buffer_b = cx_b .background() - .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .spawn(project_b.update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.rs"), cx) + })) .await .unwrap(); @@ -2477,7 +2505,9 @@ mod tests { // Open the file on client B. let buffer_b = cx_b .background() - .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .spawn(project_b.update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.rs"), cx) + })) .await .unwrap(); @@ -2616,7 +2646,9 @@ mod tests { // Open the file on client B. let buffer_b = cx_b .background() - .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx))) + .spawn(project_b.update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "one.rs"), cx) + })) .await .unwrap(); @@ -2845,7 +2877,9 @@ mod tests { // Open the file on client B. let buffer_b = cx_b .background() - .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))) + .spawn(project_b.update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "main.rs"), cx) + })) .await .unwrap(); @@ -2992,7 +3026,9 @@ mod tests { // Cause the language server to start. let _buffer = cx_b .background() - .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx))) + .spawn(project_b.update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "one.rs"), cx) + })) .await .unwrap(); @@ -3123,7 +3159,9 @@ mod tests { let buffer_b1 = cx_b .background() - .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .spawn(project_b.update(cx_b, |p, cx| { + p.open_buffer_for_path((worktree_id, "a.rs"), cx) + })) .await .unwrap(); @@ -3139,9 +3177,13 @@ mod tests { 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_for_path((worktree_id, "b.rs"), cx) + }); } 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_for_path((worktree_id, "b.rs"), cx) + }); definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx)); } @@ -4762,7 +4804,7 @@ mod tests { ); let buffer = project .update(&mut cx, |project, cx| { - project.open_buffer(project_path, cx) + project.open_buffer_for_path(project_path, cx) }) .await .unwrap(); @@ -4879,7 +4921,7 @@ mod tests { ); let buffer = project .update(&mut cx, |project, cx| { - project.open_buffer(project_path.clone(), cx) + project.open_buffer_for_path(project_path.clone(), cx) }) .await .unwrap(); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 7a7b73199e35fa90169ba3b248651c1136410605..73420e213a9337b38b3067842b3b8dff43de82b1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -258,14 +258,13 @@ impl Pane { if let Some(item) = item.log_err() { pane.update(&mut cx, |pane, cx| { pane.nav_history.borrow_mut().set_mode(mode); - let item = pane.open_item(item, cx); + pane.open_item(item, cx); pane.nav_history .borrow_mut() .set_mode(NavigationMode::Normal); if let Some(data) = entry.data { item.navigate(data, cx); } - item }); } else { workspace @@ -281,34 +280,43 @@ impl Pane { } } - pub fn open_item( + pub(crate) fn open_editor( &mut self, - item_view_to_open: Box, + project_entry_id: ProjectEntryId, cx: &mut ViewContext, + build_editor: impl FnOnce(&mut MutableAppContext) -> Box, ) -> Box { - // Find an existing view for the same project entry. - for (ix, (entry_id, item_view)) in self.item_views.iter().enumerate() { - if *entry_id == item_view_to_open.project_entry_id(cx) { + for (ix, (existing_entry_id, item_view)) in self.item_views.iter().enumerate() { + if *existing_entry_id == Some(project_entry_id) { let item_view = item_view.boxed_clone(); self.activate_item(ix, cx); return item_view; } } - item_view_to_open.set_nav_history(self.nav_history.clone(), cx); - self.add_item_view(item_view_to_open.boxed_clone(), cx); - item_view_to_open + let item_view = build_editor(cx); + self.add_item(Some(project_entry_id), item_view.boxed_clone(), cx); + item_view + } + + pub fn open_item( + &mut self, + item_view_to_open: Box, + cx: &mut ViewContext, + ) { + self.add_item(None, item_view_to_open.boxed_clone(), cx); } - pub fn add_item_view( + pub(crate) fn add_item( &mut self, - mut item_view: Box, + project_entry_id: Option, + mut item: Box, cx: &mut ViewContext, ) { - item_view.added_to_pane(cx); + item.set_nav_history(self.nav_history.clone(), cx); + item.added_to_pane(cx); let item_idx = cmp::min(self.active_item_index + 1, self.item_views.len()); - self.item_views - .insert(item_idx, (item_view.project_entry_id(cx), item_view)); + self.item_views.insert(item_idx, (project_entry_id, item)); self.activate_item(item_idx, cx); cx.notify(); } @@ -323,6 +331,16 @@ impl Pane { .map(|(_, view)| view.clone()) } + pub fn project_entry_id_for_item(&self, item: &dyn ItemViewHandle) -> Option { + self.item_views.iter().find_map(|(entry_id, existing)| { + if existing.id() == item.id() { + *entry_id + } else { + None + } + }) + } + pub fn item_for_entry(&self, entry_id: ProjectEntryId) -> Option> { self.item_views.iter().find_map(|(id, view)| { if *id == Some(entry_id) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9692a8c90030b5d3e0b89456f2b465f9ec436468..d11e773c3fef0b604c7a67cdd29ae8bfc75818c4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -9,6 +9,7 @@ mod status_bar; use anyhow::{anyhow, Result}; use client::{Authenticate, ChannelList, Client, User, UserStore}; use clock::ReplicaId; +use futures::TryFutureExt; use gpui::{ action, color::Color, @@ -21,7 +22,7 @@ use gpui::{ MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use language::LanguageRegistry; +use language::{Buffer, LanguageRegistry}; use log::error; pub use pane::*; pub use pane_group::*; @@ -41,6 +42,15 @@ use std::{ }; use theme::{Theme, ThemeRegistry}; +pub type BuildEditor = Box< + dyn Fn( + usize, + ModelHandle, + ModelHandle, + &mut MutableAppContext, + ) -> Box, +>; + action!(Open, Arc); action!(OpenNew, Arc); action!(OpenPaths, OpenParams); @@ -95,6 +105,16 @@ pub fn init(cx: &mut MutableAppContext) { ]); } +pub fn register_editor_builder(cx: &mut MutableAppContext, build_editor: F) +where + V: ItemView, + F: 'static + Fn(ModelHandle, ModelHandle, &mut ViewContext) -> V, +{ + cx.add_app_state::(Box::new(|window_id, project, model, cx| { + Box::new(cx.add_view(window_id, |cx| build_editor(project, model, cx))) + })); +} + pub struct AppState { pub languages: Arc, pub themes: Arc, @@ -138,7 +158,6 @@ pub trait ItemView: View { fn navigate(&mut self, _: Box, _: &mut ViewContext) {} fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; - fn project_entry_id(&self, cx: &AppContext) -> Option; fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext); fn clone_on_split(&self, _: &mut ViewContext) -> Option where @@ -191,7 +210,6 @@ pub trait ItemView: View { pub trait ItemViewHandle: 'static { fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; - fn project_entry_id(&self, cx: &AppContext) -> Option; fn boxed_clone(&self) -> Box; fn set_nav_history(&self, nav_history: Rc>, cx: &mut MutableAppContext); fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; @@ -239,10 +257,6 @@ impl ItemViewHandle for ViewHandle { self.read(cx).project_path(cx) } - fn project_entry_id(&self, cx: &AppContext) -> Option { - self.read(cx).project_entry_id(cx) - } - fn boxed_clone(&self) -> Box { Box::new(self.clone()) } @@ -656,6 +670,24 @@ impl Workspace { path: ProjectPath, cx: &mut ViewContext, ) -> Task, Arc>> { + let project_entry = self.project.read(cx).entry_for_path(&path, cx); + + let existing_entry = self + .active_pane() + .update(cx, |pane, cx| pane.activate_project_entry(project_entry)); + + cx.spawn(|this, cx| { + if let Some(existing_entry) = existing_entry { + return Ok(existing_entry); + } + + let load_task = this + .update(&mut cx, |this, cx| { + this.load_project_entry(project_entry, cx) + }) + .await; + }); + let load_task = self.load_path(path, cx); let pane = self.active_pane().clone().downgrade(); cx.as_mut().spawn(|mut cx| async move { @@ -663,7 +695,7 @@ impl Workspace { let pane = pane .upgrade(&cx) .ok_or_else(|| anyhow!("could not upgrade pane reference"))?; - Ok(pane.update(&mut cx, |pane, cx| pane.open_item(item, cx))) + Ok(pane.update(&mut cx, |pane, cx| pane.open_editor(item, cx))) }) } @@ -672,13 +704,23 @@ impl Workspace { path: ProjectPath, cx: &mut ViewContext, ) -> Task>> { - let project_entry = self.project.read(cx).entry_for_path(&path, cx); + if let Some(project_entry) = self.project.read(cx).entry_for_path(&path, cx) { + self.load_project_entry(project_entry, cx) + } else { + Task::ready(Err(anyhow!("no such file {:?}", path))) + } + } - if let Some(existing_item) = project_entry.and_then(|entry| { - self.panes - .iter() - .find_map(|pane| pane.read(cx).item_for_entry(entry)) - }) { + pub fn load_project_entry( + &mut self, + project_entry: ProjectEntryId, + cx: &mut ViewContext, + ) -> Task>> { + if let Some(existing_item) = self + .panes + .iter() + .find_map(|pane| pane.read(cx).item_for_entry(project_entry)) + { return Task::ready(Ok(existing_item)); } @@ -829,37 +871,108 @@ impl Workspace { pane } - pub fn open_item( - &mut self, - item_view: Box, - cx: &mut ViewContext, - ) -> Box { + pub fn open_item(&mut self, item_view: Box, cx: &mut ViewContext) { self.active_pane() .update(cx, |pane, cx| pane.open_item(item_view, cx)) } - pub fn open_item_for_project_entry( + pub fn open_editor( &mut self, project_entry: ProjectEntryId, cx: &mut ViewContext, - build_view: F, - ) -> Box - where - T: ItemView, - F: FnOnce(&mut ViewContext) -> T, - { - if let Some(existing_item) = self - .panes - .iter() - .find_map(|pane| pane.read(cx).item_for_entry(project_entry)) - { - return existing_item.boxed_clone(); - } + ) -> Task, Arc>> { + let pane = self.active_pane().clone(); + let project = self.project().clone(); + let buffer = project.update(cx, |project, cx| { + project.open_buffer_for_entry(project_entry, cx) + }); - let view = Box::new(cx.add_view(build_view)); - self.open_item(view, cx) + cx.spawn(|this, cx| async move { + let buffer = buffer.await?; + let editor = this.update(&mut cx, |this, cx| { + let window_id = cx.window_id(); + pane.update(cx, |pane, cx| { + pane.open_editor(project_entry, cx, |cx| { + cx.app_state::()(window_id, project, buffer, cx) + }) + }) + }); + Ok(editor) + }) } + // pub fn open_path( + // &mut self, + // path: ProjectPath, + // cx: &mut ViewContext, + // ) -> Task, Arc>> { + // let project_entry = self.project.read(cx).entry_for_path(&path, cx); + + // let existing_entry = self + // .active_pane() + // .update(cx, |pane, cx| pane.activate_project_entry(project_entry)); + + // cx.spawn(|this, cx| { + // if let Some(existing_entry) = existing_entry { + // return Ok(existing_entry); + // } + + // let load_task = this + // .update(&mut cx, |this, cx| { + // this.load_project_entry(project_entry, cx) + // }) + // .await; + // }); + + // let load_task = self.load_path(path, cx); + // let pane = self.active_pane().clone().downgrade(); + // cx.as_mut().spawn(|mut cx| async move { + // let item = load_task.await?; + // let pane = pane + // .upgrade(&cx) + // .ok_or_else(|| anyhow!("could not upgrade pane reference"))?; + // Ok(pane.update(&mut cx, |pane, cx| pane.open_editor(item, cx))) + // }) + // } + + // pub fn load_path( + // &mut self, + // path: ProjectPath, + // cx: &mut ViewContext, + // ) -> Task>> { + // if let Some(project_entry) = self.project.read(cx).entry_for_path(&path, cx) { + // self.load_project_entry(project_entry, cx) + // } else { + // Task::ready(Err(anyhow!("no such file {:?}", path))) + // } + // } + + // pub fn load_project_entry( + // &mut self, + // project_entry: ProjectEntryId, + // cx: &mut ViewContext, + // ) -> Task>> { + // if let Some(existing_item) = self + // .panes + // .iter() + // .find_map(|pane| pane.read(cx).item_for_entry(project_entry)) + // { + // return Task::ready(Ok(existing_item)); + // } + + // let project_path = path.clone(); + // let path_openers = self.path_openers.clone(); + // let window_id = cx.window_id(); + // self.project.update(cx, |project, cx| { + // for opener in path_openers.iter() { + // if let Some(task) = opener.open(project, project_path.clone(), window_id, cx) { + // return task; + // } + // } + // Task::ready(Err(anyhow!("no opener found for path {:?}", project_path))) + // }) + // } + pub fn activate_item(&mut self, item: &dyn ItemViewHandle, cx: &mut ViewContext) -> bool { let result = self.panes.iter().find_map(|pane| { if let Some(ix) = pane.read(cx).index_for_item(item) { @@ -930,10 +1043,11 @@ impl Workspace { let new_pane = self.add_pane(cx); self.activate_pane(new_pane.clone(), cx); if let Some(item) = pane.read(cx).active_item() { - let nav_history = new_pane.read(cx).nav_history().clone(); + let project_entry_id = pane.read(cx).project_entry_id_for_item(item.as_ref()); if let Some(clone) = item.clone_on_split(cx.as_mut()) { - clone.set_nav_history(nav_history, cx); - new_pane.update(cx, |new_pane, cx| new_pane.add_item_view(clone, cx)); + new_pane.update(cx, |new_pane, cx| { + new_pane.open_item(project_entry_id, clone, cx); + }); } } self.center.split(&pane, &new_pane, direction).unwrap();