recent_projects: Add "open in new window" action to opened projects (#55168)

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.

<img width="500" alt="Screenshot 2026-04-29 at 11  28@2x"
src="https://github.com/user-attachments/assets/b5c890fd-d21b-483b-b428-0f96b40aa7fc"
/>

Release Notes:

- Added the ability to move a currently open project to a new window
through the recent projects modal.
This commit is contained in:
Danilo Leal authored and GitHub committed 2026-04-29 11:38:03 +00:00
1 parent 2edf9c4f4a
commit 08d54c3233
4 files changed
+99 -13

No files matched your search

-7
View File
@@ -1,7 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.4381 11.5973V4.40274C14.4381 3.7405 13.8616 3.20366 13.1505 3.20366H2.84956C2.13843 3.20366 1.56195 3.7405 1.56195 4.40274V11.5973C1.56195 12.2595 2.13843 12.7963 2.84956 12.7963H5.69262" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.71237 3.20366V5.75366" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.56195 5.75365H14.4381" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.13715 3.20366V5.75366" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.01288 13.0158H10.8129M10.8129 13.0158H12.6129M10.8129 13.0158V11.2158M10.8129 13.0158V14.8158" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 941 B

-1
View File
@@ -184,7 +184,6 @@ pub enum IconName {
NewThread,
Notepad,
OpenFolder,
OpenNewWindow,
Option,
PageDown,
PageUp,
@@ -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()
+95 -4
View File
@@ -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::<MultiWorkspace>() {
@@ -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<Picker<Self>>) -> Option<AnyElement> {
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::<MultiWorkspace>() {
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<Workspace>,
create_new_window: bool,