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/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/journal/src/journal.rs b/crates/journal/src/journal.rs index 7aa8be4d97..19c044e65f 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/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 5be00513a5..e8048e0953 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::{ @@ -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 }) } @@ -2136,23 +2141,43 @@ pub fn open_paths( } } - 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 app_state = app_state.clone(); + let abs_paths = abs_paths.to_vec(); + cx.spawn(|mut cx| async move { + 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); - let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx)); - cx.spawn(|_| async move { - let items = task.await; + 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, + ); + 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) }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 2037df84a1..d4f06f074e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -367,7 +367,7 @@ async fn handle_cli_connection( .await; let mut errored = false; - let mut futures = Vec::new(); + let mut item_release_futures = Vec::new(); cx.update(|cx| { for (item, path) in items.into_iter().zip(&paths) { match item { @@ -380,7 +380,7 @@ async fn handle_cli_connection( }), ) .detach(); - futures.push(released.1); + item_release_futures.push(released.1); } Some(Err(err)) => { responses @@ -408,7 +408,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(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 33525400dd..71ce6f065a 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) }) } }) @@ -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| { @@ -536,8 +538,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 +557,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 +581,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 +614,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 +671,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;