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:
1 parent
2edf9c4f4a
commit
08d54c3233
4 files changed
+99
-13
No files matched your search
@@ -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 |
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user