From b9c197040159ab67c63a0abb61b084dc75d51101 Mon Sep 17 00:00:00 2001 From: James Cagalawan Date: Thu, 12 Feb 2026 05:55:56 -0500 Subject: [PATCH] cli: Use Terminal Panel's window for `zed --add` (#45073) This makes the `zed --add ` command use the window it was run from when run from a Terminal Panel. I run into this paper cut quite a lot working with multiple projects and preferring to create new files through the CLI instead of the GUI. **Before** With two windows open, running `zed --add a.txt` in one window's terminal might open the file `a.txt` in the other window. [Screencast from 12-17-2025 02:24:45 AM - zed --add bug before.webm](https://github.com/user-attachments/assets/30816add-91e1-41c3-b2e3-6a0e6e88771a) **After** With this change, it will use the window of the Terminal Panel. [Screencast from 12-17-2025 02:16:09 AM - zed --add bug after.webm](https://github.com/user-attachments/assets/5141518e-5fb0-47d1-9281-54c0699ff7f5) Release Notes: - Improved `zed --add` command to prefer window it was run from Co-authored-by: Kirill Bulatov --- crates/workspace/src/workspace.rs | 4 +- crates/zed/src/zed/open_listener.rs | 168 +++++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 2 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8ba34888d68533b3714a61b0adefb2f21819c512..0b599ee8ce1588afbea695e2027cb20eebdd8e57 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -8575,7 +8575,9 @@ pub fn open_paths( } }); - if open_options.open_new_workspace.is_none() + if (open_options.open_new_workspace.is_none() + || (open_options.open_new_workspace == Some(false) + && open_options.prefer_focused_window)) && (existing.is_none() || open_options.prefer_focused_window) && all_metadatas.iter().all(|file| !file.is_dir) { diff --git a/crates/zed/src/zed/open_listener.rs b/crates/zed/src/zed/open_listener.rs index 2fdefff246a9cfd32bd27797451a545d2ab5e565..293ba9059be565995332c55596050f1c0c9f447d 100644 --- a/crates/zed/src/zed/open_listener.rs +++ b/crates/zed/src/zed/open_listener.rs @@ -619,7 +619,7 @@ async fn open_local_workspace( workspace::OpenOptions { open_new_workspace, replace_window, - prefer_focused_window: wait, + prefer_focused_window: wait || open_new_workspace == Some(false), env: env.cloned(), ..Default::default() }, @@ -1249,4 +1249,170 @@ mod tests { _ => panic!("Expected GitClone kind"), } } + + #[gpui::test] + async fn test_add_flag_prefers_focused_window(cx: &mut TestAppContext) { + let app_state = init_test(cx); + + let root_dir = if cfg!(windows) { "C:\\root" } else { "/root" }; + let file1_path = if cfg!(windows) { + "C:\\root\\file1.txt" + } else { + "/root/file1.txt" + }; + let file2_path = if cfg!(windows) { + "C:\\root\\file2.txt" + } else { + "/root/file2.txt" + }; + + app_state.fs.create_dir(Path::new(root_dir)).await.unwrap(); + app_state + .fs + .create_file(Path::new(file1_path), Default::default()) + .await + .unwrap(); + app_state + .fs + .save( + Path::new(file1_path), + &Rope::from("content1"), + LineEnding::Unix, + ) + .await + .unwrap(); + app_state + .fs + .create_file(Path::new(file2_path), Default::default()) + .await + .unwrap(); + app_state + .fs + .save( + Path::new(file2_path), + &Rope::from("content2"), + LineEnding::Unix, + ) + .await + .unwrap(); + + let (response_tx, _response_rx) = ipc::channel::().unwrap(); + + // Open first workspace + let workspace_paths_1 = vec![file1_path.to_string()]; + let _errored = cx + .spawn({ + let app_state = app_state.clone(); + let response_tx = response_tx.clone(); + |mut cx| async move { + open_local_workspace( + workspace_paths_1, + Vec::new(), + false, + None, + false, + false, + &response_tx, + None, + &app_state, + &mut cx, + ) + .await + } + }) + .await; + + assert_eq!(cx.windows().len(), 1); + let multi_workspace_1 = cx.windows()[0].downcast::().unwrap(); + + // Open second workspace in a new window + let workspace_paths_2 = vec![file2_path.to_string()]; + let _errored = cx + .spawn({ + let app_state = app_state.clone(); + let response_tx = response_tx.clone(); + |mut cx| async move { + open_local_workspace( + workspace_paths_2, + Vec::new(), + false, + Some(true), // Force new window + false, + false, + &response_tx, + None, + &app_state, + &mut cx, + ) + .await + } + }) + .await; + + assert_eq!(cx.windows().len(), 2); + let multi_workspace_2 = cx.windows()[1].downcast::().unwrap(); + + // Focus window2 + multi_workspace_2 + .update(cx, |_, window, _| { + window.activate_window(); + }) + .unwrap(); + + // Now use --add flag (open_new_workspace = Some(false)) to add a new file + // It should open in the focused window (window2), not an arbitrary window + let new_file_path = if cfg!(windows) { + "C:\\root\\new_file.txt" + } else { + "/root/new_file.txt" + }; + app_state + .fs + .create_file(Path::new(new_file_path), Default::default()) + .await + .unwrap(); + + let workspace_paths_add = vec![new_file_path.to_string()]; + let _errored = cx + .spawn({ + let app_state = app_state.clone(); + let response_tx = response_tx.clone(); + |mut cx| async move { + open_local_workspace( + workspace_paths_add, + Vec::new(), + false, + Some(false), // --add flag: open_new_workspace = Some(false) + false, + false, + &response_tx, + None, + &app_state, + &mut cx, + ) + .await + } + }) + .await; + + // Should still have 2 windows (file added to existing focused window) + assert_eq!(cx.windows().len(), 2); + + // Verify the file was added to window2 (the focused one) + multi_workspace_2 + .update(cx, |workspace, _, cx| { + let items = workspace.workspace().read(cx).items(cx).collect::>(); + // Should have 2 items now (file2.txt and new_file.txt) + assert_eq!(items.len(), 2, "Focused window should have 2 items"); + }) + .unwrap(); + + // Verify window1 still has only 1 item + multi_workspace_1 + .update(cx, |workspace, _, cx| { + let items = workspace.workspace().read(cx).items(cx).collect::>(); + assert_eq!(items.len(), 1, "Other window should still have 1 item"); + }) + .unwrap(); + } }