From 9d8e3f51c8e6b08fcb5e8375238c12659894c16f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 22 Apr 2022 10:45:46 +0200 Subject: [PATCH 1/4] Show project panel when opening a folder via the CLI --- crates/journal/src/journal.rs | 2 +- crates/workspace/src/workspace.rs | 4 +++- crates/zed/src/main.rs | 29 +++++++++++++++++++++++------ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 7aa8be4d97..026ea28718 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -43,7 +43,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut MutableAppContext) { cx.spawn(|mut cx| { async move { let (journal_dir, entry_path) = create_entry.await?; - let (workspace, _) = cx + let (workspace, _, _) = cx .update(|cx| workspace::open_paths(&[journal_dir], &app_state, cx)) .await; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5be00513a5..6ca0b3cbcd 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2115,6 +2115,7 @@ pub fn open_paths( ) -> Task<( ViewHandle, Vec, Arc>>>, + bool, )> { log::info!("open paths {:?}", abs_paths); @@ -2136,6 +2137,7 @@ pub fn open_paths( } } + let is_new_workspace = existing.is_none(); let workspace = existing.unwrap_or_else(|| { cx.add_window((app_state.build_window_options)(), |cx| { let project = Project::local( @@ -2153,7 +2155,7 @@ pub fn open_paths( let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx)); cx.spawn(|_| async move { let items = task.await; - (workspace, items) + (workspace, items, is_new_workspace) }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 2037df84a1..184864879f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -22,7 +22,11 @@ use smol::process::Command; use std::{env, fs, path::PathBuf, sync::Arc, thread, time::Duration}; use theme::{ThemeRegistry, DEFAULT_THEME_NAME}; use util::ResultExt; -use workspace::{self, AppState, OpenNew, OpenPaths}; +use workspace::{ + self, + sidebar::{Side, SidebarItemId, ToggleSidebarItem}, + AppState, OpenNew, OpenPaths, +}; use zed::{ self, build_window_options, build_workspace, fs::RealFs, @@ -362,12 +366,13 @@ async fn handle_cli_connection( if let Some(request) = requests.next().await { match request { CliRequest::Open { paths, wait } => { - let (workspace, items) = cx + let (workspace, items, is_new_workspace) = cx .update(|cx| workspace::open_paths(&paths, &app_state, cx)) .await; let mut errored = false; - let mut futures = Vec::new(); + let mut opened_directory = false; + let mut item_release_futures = Vec::new(); cx.update(|cx| { for (item, path) in items.into_iter().zip(&paths) { match item { @@ -380,7 +385,7 @@ async fn handle_cli_connection( }), ) .detach(); - futures.push(released.1); + item_release_futures.push(released.1); } Some(Err(err)) => { responses @@ -390,11 +395,23 @@ async fn handle_cli_connection( .log_err(); errored = true; } - None => {} + None => opened_directory = true, } } }); + if opened_directory && is_new_workspace { + workspace.update(&mut cx, |workspace, cx| { + workspace.toggle_sidebar_item( + &ToggleSidebarItem(SidebarItemId { + side: Side::Left, + item_index: 0, + }), + cx, + ); + }); + } + if wait { let background = cx.background(); let wait = async move { @@ -408,7 +425,7 @@ async fn handle_cli_connection( drop(workspace); let _ = done_rx.await; } else { - let _ = futures::future::try_join_all(futures).await; + let _ = futures::future::try_join_all(item_release_futures).await; }; } .fuse(); From bba65e120d9c6dcdaecfb24cf2a861ec93e61375 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 22 Apr 2022 11:06:17 +0200 Subject: [PATCH 2/4] Add only one worktree when running `zed /dir /dir/file` --- crates/file_finder/src/file_finder.rs | 2 +- crates/journal/src/journal.rs | 2 +- crates/workspace/src/workspace.rs | 75 +++++++++++++++------------ crates/zed/src/zed.rs | 52 +++++++++++++++++-- 4 files changed, 90 insertions(+), 41 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index b433193456..ae4110e798 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -469,7 +469,7 @@ mod tests { workspace .update(cx, |workspace, cx| { workspace.open_paths( - &[PathBuf::from("/root/dir1"), PathBuf::from("/root/dir2")], + vec![PathBuf::from("/root/dir1"), PathBuf::from("/root/dir2")], cx, ) }) diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 026ea28718..6e579d87a7 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -49,7 +49,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut MutableAppContext) { let opened = workspace .update(&mut cx, |workspace, cx| { - workspace.open_paths(&[entry_path], cx) + workspace.open_paths(vec![entry_path], cx) }) .await; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6ca0b3cbcd..1151fd42c7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -859,44 +859,49 @@ impl Workspace { pub fn open_paths( &mut self, - abs_paths: &[PathBuf], + mut abs_paths: Vec, cx: &mut ViewContext, ) -> Task, Arc>>>> { - let entries = abs_paths - .iter() - .cloned() - .map(|path| self.project_path_for_path(&path, cx)) - .collect::>(); - let fs = self.fs.clone(); - let tasks = abs_paths - .iter() - .cloned() - .zip(entries.into_iter()) - .map(|(abs_path, project_path)| { - cx.spawn(|this, mut cx| { - let fs = fs.clone(); - async move { - let project_path = project_path.await.ok()?; - if fs.is_file(&abs_path).await { - Some( - this.update(&mut cx, |this, cx| this.open_path(project_path, cx)) - .await, - ) - } else { - None - } - } - }) - }) - .collect::>(); - cx.foreground().spawn(async move { - let mut items = Vec::new(); - for task in tasks { - items.push(task.await); + // Sort the paths to ensure we add worktrees for parents before their children. + abs_paths.sort_unstable(); + cx.spawn(|this, mut cx| async move { + let mut entries = Vec::new(); + for path in &abs_paths { + entries.push( + this.update(&mut cx, |this, cx| this.project_path_for_path(path, cx)) + .await + .ok(), + ); } - items + + let tasks = abs_paths + .iter() + .cloned() + .zip(entries.into_iter()) + .map(|(abs_path, project_path)| { + let this = this.clone(); + cx.spawn(|mut cx| { + let fs = fs.clone(); + async move { + let project_path = project_path?; + if fs.is_file(&abs_path).await { + Some( + this.update(&mut cx, |this, cx| { + this.open_path(project_path, cx) + }) + .await, + ) + } else { + None + } + } + }) + }) + .collect::>(); + + futures::future::join_all(tasks).await }) } @@ -2152,7 +2157,9 @@ pub fn open_paths( .1 }); - let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx)); + let task = workspace.update(cx, |workspace, cx| { + workspace.open_paths(abs_paths.to_vec(), cx) + }); cx.spawn(|_| async move { let items = task.await; (workspace, items, is_new_workspace) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 33525400dd..a07dedfa97 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -283,7 +283,7 @@ fn open_config_file( workspace .update(&mut cx, |workspace, cx| { if workspace.project().read(cx).is_local() { - workspace.open_paths(&[path.to_path_buf()], cx) + workspace.open_paths(vec![path.to_path_buf()], cx) } else { let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { let project = Project::local( @@ -296,7 +296,7 @@ fn open_config_file( (app_state.build_workspace)(project, &app_state, cx) }); workspace.update(cx, |workspace, cx| { - workspace.open_paths(&[path.to_path_buf()], cx) + workspace.open_paths(vec![path.to_path_buf()], cx) }) } }) @@ -536,8 +536,10 @@ mod tests { let fs = app_state.fs.as_fake(); fs.insert_dir("/dir1").await; fs.insert_dir("/dir2").await; + fs.insert_dir("/dir3").await; fs.insert_file("/dir1/a.txt", "".into()).await; fs.insert_file("/dir2/b.txt", "".into()).await; + fs.insert_file("/dir3/c.txt", "".into()).await; let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); @@ -553,7 +555,9 @@ mod tests { // Open a file within an existing worktree. cx.update(|cx| { - workspace.update(cx, |view, cx| view.open_paths(&["/dir1/a.txt".into()], cx)) + workspace.update(cx, |view, cx| { + view.open_paths(vec!["/dir1/a.txt".into()], cx) + }) }) .await; cx.read(|cx| { @@ -575,7 +579,9 @@ mod tests { // Open a file outside of any existing worktree. cx.update(|cx| { - workspace.update(cx, |view, cx| view.open_paths(&["/dir2/b.txt".into()], cx)) + workspace.update(cx, |view, cx| { + view.open_paths(vec!["/dir2/b.txt".into()], cx) + }) }) .await; cx.read(|cx| { @@ -606,6 +612,42 @@ mod tests { "b.txt" ); }); + + // Ensure opening a directory and one of its children only adds one worktree. + cx.update(|cx| { + workspace.update(cx, |view, cx| { + view.open_paths(vec!["/dir3".into(), "/dir3/c.txt".into()], cx) + }) + }) + .await; + cx.read(|cx| { + let worktree_roots = workspace + .read(cx) + .worktrees(cx) + .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) + .collect::>(); + assert_eq!( + worktree_roots, + vec!["/dir1", "/dir2/b.txt", "/dir3"] + .into_iter() + .map(Path::new) + .collect(), + ); + assert_eq!( + workspace + .read(cx) + .active_pane() + .read(cx) + .active_item() + .unwrap() + .to_any() + .downcast::() + .unwrap() + .read(cx) + .title(cx), + "c.txt" + ); + }); } #[gpui::test] @@ -627,7 +669,7 @@ mod tests { // Open a file within an existing worktree. cx.update(|cx| { workspace.update(cx, |view, cx| { - view.open_paths(&[PathBuf::from("/root/a.txt")], cx) + view.open_paths(vec![PathBuf::from("/root/a.txt")], cx) }) }) .await; From 9730213ed72e034557f28fc96d26e695db5f150f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 22 Apr 2022 11:25:23 +0200 Subject: [PATCH 3/4] Move project browser toggling logic in `workspace::open_paths` --- crates/journal/src/journal.rs | 2 +- crates/workspace/src/workspace.rs | 22 ++++++++++++++++++---- crates/zed/src/main.rs | 23 +++-------------------- crates/zed/src/zed.rs | 6 ++++-- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 6e579d87a7..19c044e65f 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -43,7 +43,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut MutableAppContext) { cx.spawn(|mut cx| { async move { let (journal_dir, entry_path) = create_entry.await?; - let (workspace, _, _) = cx + let (workspace, _) = cx .update(|cx| workspace::open_paths(&[journal_dir], &app_state, cx)) .await; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1151fd42c7..efb9527533 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,7 +31,7 @@ pub use pane_group::*; use postage::prelude::Stream; use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, Worktree}; use settings::Settings; -use sidebar::{Side, Sidebar, ToggleSidebarItem, ToggleSidebarItemFocus}; +use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus}; use status_bar::StatusBar; pub use status_bar::StatusItemView; use std::{ @@ -2120,7 +2120,6 @@ pub fn open_paths( ) -> Task<( ViewHandle, Vec, Arc>>>, - bool, )> { log::info!("open paths {:?}", abs_paths); @@ -2160,9 +2159,24 @@ pub fn open_paths( let task = workspace.update(cx, |workspace, cx| { workspace.open_paths(abs_paths.to_vec(), cx) }); - cx.spawn(|_| async move { + cx.spawn(|mut cx| async move { let items = task.await; - (workspace, items, is_new_workspace) + let opened_dir = items.iter().any(|item| item.is_none()); + + // Toggle project browser when opening a new workspace that contains a directory. + if is_new_workspace && opened_dir { + workspace.update(&mut cx, |workspace, cx| { + workspace.toggle_sidebar_item( + &ToggleSidebarItem(SidebarItemId { + side: Side::Left, + item_index: 0, + }), + cx, + ); + cx.focus_self(); + }); + } + (workspace, items) }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 184864879f..d4f06f074e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -22,11 +22,7 @@ use smol::process::Command; use std::{env, fs, path::PathBuf, sync::Arc, thread, time::Duration}; use theme::{ThemeRegistry, DEFAULT_THEME_NAME}; use util::ResultExt; -use workspace::{ - self, - sidebar::{Side, SidebarItemId, ToggleSidebarItem}, - AppState, OpenNew, OpenPaths, -}; +use workspace::{self, AppState, OpenNew, OpenPaths}; use zed::{ self, build_window_options, build_workspace, fs::RealFs, @@ -366,12 +362,11 @@ async fn handle_cli_connection( if let Some(request) = requests.next().await { match request { CliRequest::Open { paths, wait } => { - let (workspace, items, is_new_workspace) = cx + let (workspace, items) = cx .update(|cx| workspace::open_paths(&paths, &app_state, cx)) .await; let mut errored = false; - let mut opened_directory = false; let mut item_release_futures = Vec::new(); cx.update(|cx| { for (item, path) in items.into_iter().zip(&paths) { @@ -395,23 +390,11 @@ async fn handle_cli_connection( .log_err(); errored = true; } - None => opened_directory = true, + None => {} } } }); - if opened_directory && is_new_workspace { - workspace.update(&mut cx, |workspace, cx| { - workspace.toggle_sidebar_item( - &ToggleSidebarItem(SidebarItemId { - side: Side::Left, - item_index: 0, - }), - cx, - ); - }); - } - if wait { let background = cx.background(); let wait = async move { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a07dedfa97..71ce6f065a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -360,8 +360,10 @@ mod tests { .await; assert_eq!(cx.window_ids().len(), 1); let workspace_1 = cx.root_view::(cx.window_ids()[0]).unwrap(); - workspace_1.read_with(cx, |workspace, cx| { - assert_eq!(workspace.worktrees(cx).count(), 2) + workspace_1.update(cx, |workspace, cx| { + assert_eq!(workspace.worktrees(cx).count(), 2); + assert!(workspace.left_sidebar_mut().active_item().is_some()); + assert!(workspace.active_pane().is_focused(cx)); }); cx.update(|cx| { From 3def7a6803aecf266b225c7f6a0cf1eb6c2ef06f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 22 Apr 2022 11:45:18 +0200 Subject: [PATCH 4/4] Avoid flicker when toggling project browser on workspace open --- crates/gpui/src/app.rs | 12 ++++++ crates/workspace/src/sidebar.rs | 21 +++++----- crates/workspace/src/workspace.rs | 64 ++++++++++++++++--------------- 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 91497c178f..99c92b7412 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -566,6 +566,18 @@ impl AsyncAppContext { self.update(|cx| cx.add_view(window_id, build_view)) } + pub fn add_window( + &mut self, + window_options: WindowOptions, + build_root_view: F, + ) -> (usize, ViewHandle) + where + T: View, + F: FnOnce(&mut ViewContext) -> T, + { + self.update(|cx| cx.add_window(window_options, build_root_view)) + } + pub fn platform(&self) -> Arc { self.0.borrow().platform() } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index cd5ec32201..46ec3089e7 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -8,7 +8,8 @@ pub struct Sidebar { side: Side, items: Vec, active_item_ix: Option, - width: Rc>, + actual_width: Rc>, + custom_width: Rc>, } #[derive(Clone, Copy, Deserialize)] @@ -42,7 +43,8 @@ impl Sidebar { side, items: Default::default(), active_item_ix: None, - width: Rc::new(RefCell::new(260.)), + actual_width: Rc::new(RefCell::new(260.)), + custom_width: Rc::new(RefCell::new(260.)), } } @@ -137,12 +139,12 @@ impl Sidebar { container.add_child( Hook::new( ConstrainedBox::new(ChildView::new(active_item).boxed()) - .with_max_width(*self.width.borrow()) + .with_max_width(*self.custom_width.borrow()) .boxed(), ) .on_after_layout({ - let width = self.width.clone(); - move |size, _| *width.borrow_mut() = size.x() + let actual_width = self.actual_width.clone(); + move |size, _| *actual_width.borrow_mut() = size.x() }) .flex(1., false) .boxed(), @@ -157,7 +159,8 @@ impl Sidebar { } fn render_resize_handle(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { - let width = self.width.clone(); + let actual_width = self.actual_width.clone(); + let custom_width = self.custom_width.clone(); let side = self.side; MouseEventHandler::new::(side as usize, cx, |_, _| { Container::new(Empty::new().boxed()) @@ -171,10 +174,10 @@ impl Sidebar { }) .with_cursor_style(CursorStyle::ResizeLeftRight) .on_drag(move |delta, cx| { - let prev_width = *width.borrow(); + let prev_width = *actual_width.borrow(); match side { - Side::Left => *width.borrow_mut() = 0f32.max(prev_width + delta.x()), - Side::Right => *width.borrow_mut() = 0f32.max(prev_width - delta.x()), + Side::Left => *custom_width.borrow_mut() = 0f32.max(prev_width + delta.x()), + Side::Right => *custom_width.borrow_mut() = 0f32.max(prev_width - delta.x()), } cx.notify(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index efb9527533..e8048e0953 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2141,41 +2141,43 @@ pub fn open_paths( } } - let is_new_workspace = existing.is_none(); - let workspace = existing.unwrap_or_else(|| { - cx.add_window((app_state.build_window_options)(), |cx| { - let project = Project::local( - app_state.client.clone(), - app_state.user_store.clone(), - app_state.languages.clone(), - app_state.fs.clone(), - cx, - ); - (app_state.build_workspace)(project, &app_state, cx) - }) - .1 - }); - - let task = workspace.update(cx, |workspace, cx| { - workspace.open_paths(abs_paths.to_vec(), cx) - }); + let app_state = app_state.clone(); + let abs_paths = abs_paths.to_vec(); cx.spawn(|mut cx| async move { - let items = task.await; - let opened_dir = items.iter().any(|item| item.is_none()); + let workspace = if let Some(existing) = existing { + existing + } else { + let contains_directory = + futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path))) + .await + .contains(&false); - // Toggle project browser when opening a new workspace that contains a directory. - if is_new_workspace && opened_dir { - workspace.update(&mut cx, |workspace, cx| { - workspace.toggle_sidebar_item( - &ToggleSidebarItem(SidebarItemId { - side: Side::Left, - item_index: 0, - }), + cx.add_window((app_state.build_window_options)(), |cx| { + let project = Project::local( + app_state.client.clone(), + app_state.user_store.clone(), + app_state.languages.clone(), + app_state.fs.clone(), cx, ); - cx.focus_self(); - }); - } + let mut workspace = (app_state.build_workspace)(project, &app_state, cx); + if contains_directory { + workspace.toggle_sidebar_item( + &ToggleSidebarItem(SidebarItemId { + side: Side::Left, + item_index: 0, + }), + cx, + ); + } + workspace + }) + .1 + }; + + let items = workspace + .update(&mut cx, |workspace, cx| workspace.open_paths(abs_paths, cx)) + .await; (workspace, items) }) }