diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 523a961d6964e2c6e08d03b75a3e1eb1890fc586..98053432c5a186ecc886318f2d677f73a62295a2 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -1123,6 +1123,8 @@ "bindings": { "ctrl-k": "recent_projects::ToggleActionsMenu", "ctrl-shift-a": "workspace::AddFolderToProject", + "shift-backspace": "recent_projects::RemoveSelected", + "ctrl-shift-enter": "recent_projects::AddToWorkspace", }, }, { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 9ca71aa9be3a99b1b52ab8490a6fe841956ecf50..f0835a139a39602547d9d8da1cba93eaa7ee82a9 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -1188,6 +1188,8 @@ "bindings": { "cmd-k": "recent_projects::ToggleActionsMenu", "cmd-shift-a": "workspace::AddFolderToProject", + "shift-backspace": "recent_projects::RemoveSelected", + "cmd-shift-enter": "recent_projects::AddToWorkspace", }, }, { diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 1883d0df0b3ff44ad8dceefb997198cb203a9b8d..41f36638e1dec40890ddecc6a808c669672e9317 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -1134,6 +1134,8 @@ "bindings": { "ctrl-k": "recent_projects::ToggleActionsMenu", "ctrl-shift-a": "workspace::AddFolderToProject", + "shift-backspace": "recent_projects::RemoveSelected", + "ctrl-shift-enter": "recent_projects::AddToWorkspace", }, }, { diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 7194e8868fd2a0015edd5c18c96f2fe164206fb7..e1a0cb0609a9883bfe73048eda64cc8d1b299c2e 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -52,7 +52,10 @@ use workspace::{ }; use zed_actions::{OpenDevContainer, OpenRecent, OpenRemote}; -actions!(recent_projects, [ToggleActionsMenu]); +actions!( + recent_projects, + [ToggleActionsMenu, RemoveSelected, AddToWorkspace,] +); #[derive(Clone, Debug)] pub struct RecentProjectEntry { @@ -684,6 +687,79 @@ impl RecentProjects { } }); } + + fn handle_remove_selected( + &mut self, + _: &RemoveSelected, + window: &mut Window, + cx: &mut Context, + ) { + self.picker.update(cx, |picker, cx| { + let ix = picker.delegate.selected_index; + + match picker.delegate.filtered_entries.get(ix) { + Some(ProjectPickerEntry::OpenFolder { index, .. }) => { + if let Some(folder) = picker.delegate.open_folders.get(*index) { + let worktree_id = folder.worktree_id; + let Some(workspace) = picker.delegate.workspace.upgrade() else { + return; + }; + workspace.update(cx, |workspace, cx| { + let project = workspace.project().clone(); + project.update(cx, |project, cx| { + project.remove_worktree(worktree_id, cx); + }); + }); + picker.delegate.open_folders = get_open_folders(workspace.read(cx), cx); + let query = picker.query(cx); + picker.update_matches(query, window, cx); + } + } + Some(ProjectPickerEntry::OpenProject(hit)) => { + if let Some((workspace_id, ..)) = + picker.delegate.workspaces.get(hit.candidate_id) + { + let workspace_id = *workspace_id; + picker + .delegate + .remove_sibling_workspace(workspace_id, window, cx); + let query = picker.query(cx); + picker.update_matches(query, window, cx); + } + } + Some(ProjectPickerEntry::RecentProject(_)) => { + picker.delegate.delete_recent_project(ix, window, cx); + } + _ => {} + } + }); + } + + fn handle_add_to_workspace( + &mut self, + _: &AddToWorkspace, + window: &mut Window, + cx: &mut Context, + ) { + self.picker.update(cx, |picker, cx| { + let ix = picker.delegate.selected_index; + + if let Some(ProjectPickerEntry::RecentProject(hit)) = + picker.delegate.filtered_entries.get(ix) + { + if let Some((_, location, paths, _)) = + picker.delegate.workspaces.get(hit.candidate_id) + { + if matches!(location, SerializedWorkspaceLocation::Local) { + let paths_to_add = paths.paths().to_vec(); + picker + .delegate + .add_project_to_workspace(paths_to_add, window, cx); + } + } + } + }); + } } impl EventEmitter for RecentProjects {} @@ -699,6 +775,8 @@ impl Render for RecentProjects { v_flex() .key_context("RecentProjects") .on_action(cx.listener(Self::handle_toggle_open_menu)) + .on_action(cx.listener(Self::handle_remove_selected)) + .on_action(cx.listener(Self::handle_add_to_workspace)) .w(rems(self.rem_width)) .child(self.picker.clone()) } @@ -1364,7 +1442,6 @@ impl PickerDelegate for RecentProjectsDelegate { ) } ProjectPickerEntry::RecentProject(hit) => { - let popover_style = matches!(self.style, ProjectPickerStyle::Popover); let (_, location, paths, _) = self.workspaces.get(hit.candidate_id)?; let is_local = matches!(location, SerializedWorkspaceLocation::Local); let paths_to_add = paths.paths().to_vec(); @@ -1432,28 +1509,26 @@ impl PickerDelegate for RecentProjectsDelegate { }), ) }) - .when(popover_style, |this| { - this.child( - IconButton::new("open_new_window", IconName::ArrowUpRight) - .icon_size(IconSize::XSmall) - .tooltip({ - move |_, cx| { - Tooltip::for_action_in( - "Open Project in New Window", - &menu::SecondaryConfirm, - &focus_handle, - cx, - ) - } - }) - .on_click(cx.listener(move |this, _event, window, cx| { - cx.stop_propagation(); - window.prevent_default(); - this.delegate.set_selected_index(ix, window, cx); - this.delegate.confirm(true, window, cx); - })), - ) - }) + .child( + IconButton::new("open_new_window", IconName::ArrowUpRight) + .icon_size(IconSize::XSmall) + .tooltip({ + move |_, cx| { + Tooltip::for_action_in( + "Open Project in New Window", + &menu::SecondaryConfirm, + &focus_handle, + cx, + ) + } + }) + .on_click(cx.listener(move |this, _event, window, cx| { + cx.stop_propagation(); + window.prevent_default(); + this.delegate.set_selected_index(ix, window, cx); + this.delegate.confirm(true, window, cx); + })), + ) .child( IconButton::new("delete", IconName::Close) .icon_size(IconSize::Small) @@ -1518,9 +1593,7 @@ impl PickerDelegate for RecentProjectsDelegate { .border_t_1() .border_color(cx.theme().colors().border_variant) .child({ - let open_action = workspace::Open { - create_new_window: self.create_new_window, - }; + let open_action = workspace::Open::default(); Button::new("open_local_folder", "Open Local Project") .key_binding(KeyBinding::for_action_in(&open_action, &focus_handle, cx)) .on_click(move |_, window, cx| { @@ -1551,6 +1624,44 @@ impl PickerDelegate for RecentProjectsDelegate { ); } + let selected_entry = self.filtered_entries.get(self.selected_index); + + let secondary_footer_actions: Option = match selected_entry { + Some(ProjectPickerEntry::OpenFolder { .. } | ProjectPickerEntry::OpenProject(_)) => { + let label = if matches!(selected_entry, Some(ProjectPickerEntry::OpenFolder { .. })) + { + "Remove Folder" + } else { + "Remove from Window" + }; + Some( + Button::new("remove_selected", label) + .key_binding(KeyBinding::for_action_in( + &RemoveSelected, + &focus_handle, + cx, + )) + .on_click(|_, window, cx| { + window.dispatch_action(RemoveSelected.boxed_clone(), cx) + }) + .into_any_element(), + ) + } + Some(ProjectPickerEntry::RecentProject(_)) => Some( + Button::new("delete_recent", "Delete") + .key_binding(KeyBinding::for_action_in( + &RemoveSelected, + &focus_handle, + cx, + )) + .on_click(|_, window, cx| { + window.dispatch_action(RemoveSelected.boxed_clone(), cx) + }) + .into_any_element(), + ), + _ => None, + }; + Some( h_flex() .flex_1() @@ -1559,6 +1670,9 @@ impl PickerDelegate for RecentProjectsDelegate { .justify_end() .border_t_1() .border_color(cx.theme().colors().border_variant) + .when_some(secondary_footer_actions, |this, actions| { + this.child(actions) + }) .map(|this| { if is_already_open_entry { this.child( @@ -1607,7 +1721,7 @@ impl PickerDelegate for RecentProjectsDelegate { y: px(-2.0), }) .trigger( - Button::new("actions-trigger", "Actions…") + Button::new("actions-trigger", "Actions") .selected_style(ButtonStyle::Tinted(TintColor::Accent)) .key_binding(KeyBinding::for_action_in( &ToggleActionsMenu, @@ -1617,16 +1731,32 @@ impl PickerDelegate for RecentProjectsDelegate { ) .menu({ let focus_handle = focus_handle.clone(); - let create_new_window = self.create_new_window; + let show_add_to_workspace = match selected_entry { + Some(ProjectPickerEntry::RecentProject(hit)) => self + .workspaces + .get(hit.candidate_id) + .map(|(_, loc, ..)| { + matches!(loc, SerializedWorkspaceLocation::Local) + }) + .unwrap_or(false), + _ => false, + }; move |window, cx| { Some(ContextMenu::build(window, cx, { let focus_handle = focus_handle.clone(); move |menu, _, _| { menu.context(focus_handle) + .when(show_add_to_workspace, |menu| { + menu.action( + "Add to Workspace", + AddToWorkspace.boxed_clone(), + ) + .separator() + }) .action( "Open Local Project", - workspace::Open { create_new_window }.boxed_clone(), + workspace::Open::default().boxed_clone(), ) .action( "Open Remote Project",