From 08d54c3233cc46b8b2eae31ed2ede5b66ac3ef24 Mon Sep 17 00:00:00 2001
From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Date: Wed, 29 Apr 2026 08:38:03 -0300
Subject: [PATCH] recent_projects: Add "open in new window" action to opened
projects (#55168)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This PR adds a "open in new window" button to the projects that are
already open in the current window. This matches a capability that was
only available through the threads sidebar.
Release Notes:
- Added the ability to move a currently open project to a new window
through the recent projects modal.
---
assets/icons/open_new_window.svg | 7 --
crates/icons/src/icons.rs | 1 -
.../src/highlighted_match_with_paths.rs | 5 +-
crates/recent_projects/src/recent_projects.rs | 99 ++++++++++++++++++-
4 files changed, 99 insertions(+), 13 deletions(-)
delete mode 100644 assets/icons/open_new_window.svg
diff --git a/assets/icons/open_new_window.svg b/assets/icons/open_new_window.svg
deleted file mode 100644
index c81d49f9ff..0000000000
--- a/assets/icons/open_new_window.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs
index b49ad8e340..d67c4f76e6 100644
--- a/crates/icons/src/icons.rs
+++ b/crates/icons/src/icons.rs
@@ -184,7 +184,6 @@ pub enum IconName {
NewThread,
Notepad,
OpenFolder,
- OpenNewWindow,
Option,
PageDown,
PageUp,
diff --git a/crates/picker/src/highlighted_match_with_paths.rs b/crates/picker/src/highlighted_match_with_paths.rs
index 7c88213437..62bae44312 100644
--- a/crates/picker/src/highlighted_match_with_paths.rs
+++ b/crates/picker/src/highlighted_match_with_paths.rs
@@ -52,7 +52,9 @@ impl HighlightedMatch {
}
impl RenderOnce for HighlightedMatch {
fn render(self, _window: &mut Window, _: &mut App) -> impl IntoElement {
- HighlightedLabel::new(self.text, self.highlight_positions).color(self.color)
+ HighlightedLabel::new(self.text, self.highlight_positions)
+ .color(self.color)
+ .truncate()
}
}
@@ -74,6 +76,7 @@ impl HighlightedMatchWithPaths {
impl RenderOnce for HighlightedMatchWithPaths {
fn render(mut self, _window: &mut Window, _: &mut App) -> impl IntoElement {
v_flex()
+ .min_w_0()
.child(
h_flex()
.gap_1()
diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs
index b93fa009fc..f5b5f78fd2 100644
--- a/crates/recent_projects/src/recent_projects.rs
+++ b/crates/recent_projects/src/recent_projects.rs
@@ -1115,6 +1115,12 @@ impl PickerDelegate for RecentProjectsDelegate {
return;
};
+ if secondary && key.host().is_none() && self.window_project_groups.len() >= 2 {
+ move_project_group_to_new_window(key, window, cx);
+ cx.emit(DismissEvent);
+ return;
+ }
+
let key = key.clone();
let path_list = key.path_list().clone();
if let Some(handle) = window.window_handle().downcast::() {
@@ -1406,8 +1412,40 @@ impl PickerDelegate for RecentProjectsDelegate {
};
let project_group_key = key.clone();
+ let is_local = key.host().is_none();
+ let has_multiple_groups = self.window_project_groups.len() >= 2;
let secondary_actions = h_flex()
- .gap_1()
+ .gap_0p5()
+ .when(is_local && has_multiple_groups, |this| {
+ this.child(
+ IconButton::new("move_to_new_window", IconName::ArrowUpRight)
+ .icon_size(IconSize::Small)
+ .tooltip({
+ let focus_handle = self.focus_handle.clone();
+ move |_, cx| {
+ Tooltip::for_action_in(
+ "Open in New Window",
+ &menu::SecondaryConfirm,
+ &focus_handle,
+ cx,
+ )
+ }
+ })
+ .on_click({
+ let project_group_key = project_group_key.clone();
+ cx.listener(move |_picker, _, window, cx| {
+ cx.stop_propagation();
+ window.prevent_default();
+ move_project_group_to_new_window(
+ &project_group_key,
+ window,
+ cx,
+ );
+ cx.emit(DismissEvent);
+ })
+ }),
+ )
+ })
.when(!is_active, |this| {
this.child(
IconButton::new("remove_open_project", IconName::Close)
@@ -1433,12 +1471,13 @@ impl PickerDelegate for RecentProjectsDelegate {
Some(
ListItem::new(ix)
- .toggle_state(selected)
.inset(true)
+ .toggle_state(selected)
.spacing(ListItemSpacing::Sparse)
.child(
h_flex()
.id("open_project_info_container")
+ .w_full()
.gap_2p5()
.when(self.has_any_non_local_projects, |this| {
this.child(Icon::new(icon).color(Color::Muted))
@@ -1540,7 +1579,7 @@ impl PickerDelegate for RecentProjectsDelegate {
)
})
.child(
- IconButton::new("open_new_window", IconName::OpenNewWindow)
+ IconButton::new("open_new_window", IconName::ArrowUpRight)
.icon_size(IconSize::Small)
.tooltip({
move |_, cx| {
@@ -1616,11 +1655,23 @@ impl PickerDelegate for RecentProjectsDelegate {
fn render_footer(&self, _: &mut Window, cx: &mut Context>) -> Option {
let focus_handle = self.focus_handle.clone();
let popover_style = matches!(self.style, ProjectPickerStyle::Popover);
+
let is_already_open_entry = matches!(
self.filtered_entries.get(self.selected_index),
Some(ProjectPickerEntry::OpenFolder { .. } | ProjectPickerEntry::ProjectGroup(_))
);
+ let show_move_to_new_window = match self.filtered_entries.get(self.selected_index) {
+ Some(ProjectPickerEntry::ProjectGroup(hit)) => {
+ self.window_project_groups.len() >= 2
+ && self
+ .window_project_groups
+ .get(hit.candidate_id)
+ .is_some_and(|key| key.host().is_none())
+ }
+ _ => false,
+ };
+
if popover_style {
return Some(
v_flex()
@@ -1753,7 +1804,31 @@ impl PickerDelegate for RecentProjectsDelegate {
})
.map(|this| {
if is_already_open_entry {
- this.child(
+ this.when(show_move_to_new_window, |this| {
+ this.child({
+ let window_project_groups = self.window_project_groups.clone();
+ let selected_index = self.selected_index;
+ let filtered_entries = self.filtered_entries.clone();
+ Button::new("move_to_new_window", "New Window")
+ .key_binding(KeyBinding::for_action_in(
+ &menu::SecondaryConfirm,
+ &focus_handle,
+ cx,
+ ))
+ .on_click(move |_, window, cx| {
+ let key = match filtered_entries.get(selected_index) {
+ Some(ProjectPickerEntry::ProjectGroup(hit)) => {
+ window_project_groups.get(hit.candidate_id).cloned()
+ }
+ _ => None,
+ };
+ if let Some(key) = key {
+ move_project_group_to_new_window(&key, window, cx);
+ }
+ })
+ })
+ })
+ .child(
Button::new("activate", "Activate")
.key_binding(KeyBinding::for_action_in(
&menu::Confirm,
@@ -1930,6 +2005,22 @@ pub(crate) fn highlights_for_path(
},
)
}
+
+fn move_project_group_to_new_window(key: &ProjectGroupKey, window: &mut Window, cx: &mut App) {
+ if let Some(handle) = window.window_handle().downcast::() {
+ let key = key.clone();
+ cx.defer(move |cx| {
+ handle
+ .update(cx, |multi_workspace, window, cx| {
+ multi_workspace
+ .open_project_group_in_new_window(&key, window, cx)
+ .detach_and_log_err(cx);
+ })
+ .log_err();
+ });
+ }
+}
+
fn open_local_project(
workspace: WeakEntity,
create_new_window: bool,