From 0ff803fe10dc4b9a33390fe9a4c8a9304ba6b2af Mon Sep 17 00:00:00 2001 From: Dino Date: Mon, 20 Jan 2025 11:07:11 +0000 Subject: [PATCH] workspace: Add action to move focused panel to next dock position (#23317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This Pull Request introduces a new command `workspace: move focused panel to next position` which finds the currently focused panel, if such panel exists, and moves it to the next valid dock position, following the order of `Left → Bottom → Right` and then starting again from the left position. In order to achieve this the following changes have been introduced: * Add a new default implementation for `PanelHandle`, namely `PanelHandle::move_to_next_position` which leverages `PanelHandle::position`, `PanelHandle::position_is_valid` and `PanelHandle::set_position` methods to update the panel's position to the next valid position. * Add a new method to the `workspace` module, ` move_focused_panel_to_next_position`, which is responsible for finding the currently focused panel, if such a panel exists, and calling the `move_to_next_position` method in the panel's handle. * Add a new action to the `workspace` module, `MoveFocusedPanelToNextPosition`, which is handled by the `move_focused_panel_to_next_position` method. Tests have also been added to the `workspace` module in order to guarantee that the action is correctly updating the focused panel's position. Here's a quick video of it, in action 🔽 https://github.com/user-attachments/assets/264d382b-5239-40aa-bc5e-5d569dec0734 Closes #23115 Release Notes: - Added new command to move the focused panel to the next valid dock position – `workspace: move focused panel to next position` . --- crates/workspace/src/dock.rs | 15 ++++++ crates/workspace/src/workspace.rs | 87 +++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index d3114fcf55c936c7d9c7356fafb34a543b07c954..1dbe303288b4bc45dfa9ae63c6fed3ae67ffc7d1 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -76,6 +76,21 @@ pub trait PanelHandle: Send + Sync { fn focus_handle(&self, cx: &AppContext) -> FocusHandle; fn to_any(&self) -> AnyView; fn activation_priority(&self, cx: &AppContext) -> u32; + fn move_to_next_position(&self, cx: &mut WindowContext) { + let current_position = self.position(cx); + let next_position = [ + DockPosition::Left, + DockPosition::Bottom, + DockPosition::Right, + ] + .into_iter() + .filter(|position| self.position_is_valid(*position, cx)) + .skip_while(|valid_position| *valid_position != current_position) + .nth(1) + .unwrap_or(DockPosition::Left); + + self.set_position(next_position, cx); + } } impl PanelHandle for View diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e69b6d9353226a7330c54f82bfb11735012cf30d..20b4659c938f2cee55eb1001083e7bba2a5f4d06 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -133,6 +133,7 @@ actions!( CopyRelativePath, Feedback, FollowNextCollaborator, + MoveFocusedPanelToNextPosition, NewCenterTerminal, NewFile, NewFileSplitVertical, @@ -1749,6 +1750,29 @@ impl Workspace { .detach_and_log_err(cx) } + pub fn move_focused_panel_to_next_position( + &mut self, + _: &MoveFocusedPanelToNextPosition, + cx: &mut ViewContext, + ) { + let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; + let active_dock = docks + .into_iter() + .find(|dock| dock.focus_handle(cx).contains_focused(cx)); + + if let Some(dock) = active_dock { + dock.update(cx, |dock, cx| { + let active_panel = dock + .active_panel() + .filter(|panel| panel.focus_handle(cx).contains_focused(cx)); + + if let Some(panel) = active_panel { + panel.move_to_next_position(cx); + } + }) + } + } + pub fn prepare_to_close( &mut self, close_intent: CloseIntent, @@ -4492,6 +4516,7 @@ impl Workspace { .on_action(cx.listener(Self::close_window)) .on_action(cx.listener(Self::activate_pane_at_index)) .on_action(cx.listener(Self::move_item_to_pane_at_index)) + .on_action(cx.listener(Self::move_focused_panel_to_next_position)) .on_action(cx.listener(|workspace, _: &Unfollow, cx| { let pane = workspace.active_pane().clone(); workspace.unfollow_in_pane(&pane, cx); @@ -7971,6 +7996,68 @@ mod tests { }); } + #[gpui::test] + async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs, [], cx).await; + let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + + // Add a new panel to the right dock, opening the dock and setting the + // focus to the new panel. + let panel = workspace.update(cx, |workspace, cx| { + let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx)); + workspace.add_panel(panel.clone(), cx); + + workspace + .right_dock() + .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + workspace.toggle_panel_focus::(cx); + + panel + }); + + // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the + // panel to the next valid position which, in this case, is the left + // dock. + cx.dispatch_action(MoveFocusedPanelToNextPosition); + workspace.update(cx, |workspace, cx| { + assert!(workspace.left_dock().read(cx).is_open()); + assert_eq!(panel.read(cx).position, DockPosition::Left); + }); + + // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the + // panel to the next valid position which, in this case, is the bottom + // dock. + cx.dispatch_action(MoveFocusedPanelToNextPosition); + workspace.update(cx, |workspace, cx| { + assert!(workspace.bottom_dock().read(cx).is_open()); + assert_eq!(panel.read(cx).position, DockPosition::Bottom); + }); + + // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time + // around moving the panel to its initial position, the right dock. + cx.dispatch_action(MoveFocusedPanelToNextPosition); + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert_eq!(panel.read(cx).position, DockPosition::Right); + }); + + // Remove focus from the panel, ensuring that, if the panel is not + // focused, the `MoveFocusedPanelToNextPosition` action does not update + // the panel's position, so the panel is still in the right dock. + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); + + cx.dispatch_action(MoveFocusedPanelToNextPosition); + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert_eq!(panel.read(cx).position, DockPosition::Right); + }); + } + mod register_project_item_tests { use gpui::Context as _;