From d22f8bf2a7624a3084fc0640faec3b30f894ec4c Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Mon, 6 Apr 2026 11:43:14 -0700 Subject: [PATCH] Make `zed foo/` add to active window and open sidebar (#53254) Changes the default CLI behavior when opening directories: - `zed foo/` now adds `foo/` as a new workspace in the active window's MultiWorkspace and opens the sidebar, instead of creating a new window. If `bar/` was previously open, both `foo/` and `bar/` are retained as separate workspaces in the sidebar. - `zed -n foo/` continues to open a new window without the sidebar (unchanged behavior). - `zed -a foo/` continues to add to the existing workspace (unchanged behavior). The new behavior is gated on `multi_workspace_enabled` (the `agent-v2` feature flag). Without the flag, directories open in a new window as before, avoiding a situation where the workspace gets replaced with no sidebar to switch back. The change is contained to `open_paths()` in `crates/workspace/src/workspace.rs`. When no CLI flag is specified and no existing workspace matches the paths, we now look for the active window and set `requesting_window` so that `Workspace::new_local` adds the new workspace to it rather than creating a new window. Release Notes: - N/A --- crates/workspace/src/workspace.rs | 36 ++++++++++++++++-- crates/zed/src/zed.rs | 63 +++++++++++++++---------------- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c726d0a421928979200a088125d3ddd172530ff9..cc5d1e8635e9194522fea5506fef4084f8133c53 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -9325,7 +9325,7 @@ pub fn open_workspace_by_id( pub fn open_paths( abs_paths: &[PathBuf], app_state: Arc, - open_options: OpenOptions, + mut open_options: OpenOptions, cx: &mut App, ) -> Task> { let abs_paths = abs_paths.to_vec(); @@ -9350,10 +9350,9 @@ pub fn open_paths( let all_metadatas = futures::future::join_all(all_paths) .await .into_iter() - .filter_map(|result| result.ok().flatten()) - .collect::>(); + .filter_map(|result| result.ok().flatten()); - if all_metadatas.iter().all(|file| !file.is_dir) { + if all_metadatas.into_iter().all(|file| !file.is_dir) { cx.update(|cx| { let windows = workspace_windows_for_location( &SerializedWorkspaceLocation::Local, @@ -9375,6 +9374,35 @@ pub fn open_paths( } } + // Fallback for directories: when no flag is specified and no existing + // workspace matched, add the directory as a new workspace in the + // active window's MultiWorkspace (instead of opening a new window). + if open_options.open_new_workspace.is_none() && existing.is_none() { + let target_window = cx.update(|cx| { + let windows = workspace_windows_for_location( + &SerializedWorkspaceLocation::Local, + cx, + ); + let window = cx + .active_window() + .and_then(|window| window.downcast::()) + .filter(|window| windows.contains(window)) + .or_else(|| windows.into_iter().next()); + window.filter(|window| { + window.read(cx).is_ok_and(|mw| mw.multi_workspace_enabled(cx)) + }) + }); + + if let Some(window) = target_window { + open_options.requesting_window = Some(window); + window + .update(cx, |multi_workspace, _, cx| { + multi_workspace.open_sidebar(cx); + }) + .log_err(); + } + } + let open_in_dev_container = open_options.open_in_dev_container; let result = if let Some((existing, target_workspace)) = existing { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 795fd12a6c73d9576095b6cd4a26cdd5577e6000..ed49236a9da6b69f80c8c981eaddaa16ca69face 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2606,18 +2606,33 @@ mod tests { }) .await .unwrap(); - assert_eq!(cx.read(|cx| cx.windows().len()), 2); - - // Replace existing windows - let window = cx - .update(|cx| cx.windows()[0].downcast::()) + assert_eq!(cx.read(|cx| cx.windows().len()), 1); + cx.run_until_parked(); + multi_workspace_1 + .update(cx, |multi_workspace, _window, cx| { + assert_eq!(multi_workspace.workspaces().len(), 2); + assert!(multi_workspace.sidebar_open()); + let workspace = multi_workspace.workspace().read(cx); + assert_eq!( + workspace + .worktrees(cx) + .map(|w| w.read(cx).abs_path()) + .collect::>(), + &[ + Path::new(path!("/root/c")).into(), + Path::new(path!("/root/d")).into(), + ] + ); + }) .unwrap(); + + // Opening with -n (open_new_workspace: Some(true)) still creates a new window. cx.update(|cx| { open_paths( &[PathBuf::from(path!("/root/e"))], app_state, workspace::OpenOptions { - requesting_window: Some(window), + open_new_workspace: Some(true), ..Default::default() }, cx, @@ -2627,23 +2642,6 @@ mod tests { .unwrap(); cx.background_executor.run_until_parked(); assert_eq!(cx.read(|cx| cx.windows().len()), 2); - let multi_workspace_1 = cx - .update(|cx| cx.windows()[0].downcast::()) - .unwrap(); - multi_workspace_1 - .update(cx, |multi_workspace, window, cx| { - let workspace = multi_workspace.workspace().read(cx); - assert_eq!( - workspace - .worktrees(cx) - .map(|w| w.read(cx).abs_path()) - .collect::>(), - &[Path::new(path!("/root/e")).into()] - ); - assert!(workspace.right_dock().read(cx).is_open()); - assert!(workspace.active_pane().focus_handle(cx).is_focused(window)); - }) - .unwrap(); } #[gpui::test] @@ -2724,7 +2722,6 @@ mod tests { .await .unwrap(); assert_eq!(cx.update(|cx| cx.windows().len()), 1); - let window1 = cx.update(|cx| cx.active_window().unwrap()); cx.update(|cx| { open_paths( @@ -2738,6 +2735,8 @@ mod tests { .unwrap(); assert_eq!(cx.update(|cx| cx.windows().len()), 1); + // Opening a directory with default options adds to the existing window + // rather than creating a new one. cx.update(|cx| { open_paths( &[PathBuf::from(path!("/root/dir2"))], @@ -2748,25 +2747,23 @@ mod tests { }) .await .unwrap(); - assert_eq!(cx.update(|cx| cx.windows().len()), 2); - let window2 = cx.update(|cx| cx.active_window().unwrap()); - assert!(window1 != window2); - cx.update_window(window1, |_, window, _| window.activate_window()) - .unwrap(); + assert_eq!(cx.update(|cx| cx.windows().len()), 1); + // Opening a directory with -n creates a new window. cx.update(|cx| { open_paths( - &[PathBuf::from(path!("/root/dir2/c"))], + &[PathBuf::from(path!("/root/dir2"))], app_state.clone(), - workspace::OpenOptions::default(), + workspace::OpenOptions { + open_new_workspace: Some(true), + ..Default::default() + }, cx, ) }) .await .unwrap(); assert_eq!(cx.update(|cx| cx.windows().len()), 2); - // should have opened in window2 because that has dir2 visibly open (window1 has it open, but not in the project panel) - assert!(cx.update(|cx| cx.active_window().unwrap()) == window2); } #[gpui::test]