From fcfc54c515da6b1a5c06673fdf6c39ae798e6473 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Sep 2025 21:18:28 -0600 Subject: [PATCH] Allow SplitAndMove on panes (#38034) Updates #19350 Release Notes: - Add `pane::SplitAndMove{Up,Down,Left,Right}` to allow creating a split without cloning the current buffer. --- crates/terminal_view/src/terminal_panel.rs | 45 ++++++++++++---- crates/workspace/src/pane.rs | 63 +++++++++++++++++++--- crates/workspace/src/workspace.rs | 29 +++++++++- 3 files changed, 118 insertions(+), 19 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index e72175572b1fcc239b6c1aee12c0a822b3f6c64c..5308b230f78b334c9a39cadbcbb3117ac4e3e11f 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -385,19 +385,44 @@ impl TerminalPanel { } self.serialize(cx); } - &pane::Event::Split(direction) => { - let fut = self.new_pane_with_cloned_active_terminal(window, cx); - let pane = pane.clone(); - cx.spawn_in(window, async move |panel, cx| { - let Some(new_pane) = fut.await else { + &pane::Event::Split { + direction, + clone_active_item, + } => { + if clone_active_item { + let fut = self.new_pane_with_cloned_active_terminal(window, cx); + let pane = pane.clone(); + cx.spawn_in(window, async move |panel, cx| { + let Some(new_pane) = fut.await else { + return; + }; + panel + .update_in(cx, |panel, window, cx| { + panel.center.split(&pane, &new_pane, direction).log_err(); + window.focus(&new_pane.focus_handle(cx)); + }) + .ok(); + }) + .detach(); + } else { + let Some(item) = pane.update(cx, |pane, cx| pane.take_active_item(window, cx)) + else { return; }; - _ = panel.update_in(cx, |panel, window, cx| { - panel.center.split(&pane, &new_pane, direction).log_err(); - window.focus(&new_pane.focus_handle(cx)); + let Ok(project) = self + .workspace + .update(cx, |workspace, _| workspace.project().clone()) + else { + return; + }; + let new_pane = + new_terminal_pane(self.workspace.clone(), project, false, window, cx); + new_pane.update(cx, |pane, cx| { + pane.add_item(item, true, true, None, window, cx); }); - }) - .detach(); + self.center.split(&pane, &new_pane, direction).log_err(); + window.focus(&new_pane.focus_handle(cx)); + } } pane::Event::Focus => { self.active_pane = pane.clone(); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 71a9b89f0a91ea29793c3cfab04bcfbd0e8acc69..4ce8a890237f97db5596f55f607a603a2695ab7b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -206,14 +206,22 @@ actions!( JoinAll, /// Reopens the most recently closed item. ReopenClosedItem, - /// Splits the pane to the left. + /// Splits the pane to the left, cloning the current item. SplitLeft, - /// Splits the pane upward. + /// Splits the pane upward, cloning the current item. SplitUp, - /// Splits the pane to the right. + /// Splits the pane to the right, cloning the current item. SplitRight, - /// Splits the pane downward. + /// Splits the pane downward, cloning the current item. SplitDown, + /// Splits the pane to the left, moving the current item. + SplitAndMoveLeft, + /// Splits the pane upward, moving the current item. + SplitAndMoveUp, + /// Splits the pane to the right, moving the current item. + SplitAndMoveRight, + /// Splits the pane downward, moving the current item. + SplitAndMoveDown, /// Splits the pane horizontally. SplitHorizontal, /// Splits the pane vertically. @@ -257,7 +265,10 @@ pub enum Event { RemovedItem { item: Box, }, - Split(SplitDirection), + Split { + direction: SplitDirection, + clone_active_item: bool, + }, ItemPinned, ItemUnpinned, JoinAll, @@ -288,9 +299,13 @@ impl fmt::Debug for Event { .debug_struct("RemovedItem") .field("item", &item.item_id()) .finish(), - Event::Split(direction) => f + Event::Split { + direction, + clone_active_item, + } => f .debug_struct("Split") .field("direction", direction) + .field("clone_active_item", clone_active_item) .finish(), Event::JoinAll => f.write_str("JoinAll"), Event::JoinIntoNext => f.write_str("JoinIntoNext"), @@ -1776,6 +1791,16 @@ impl Pane { }) } + pub fn take_active_item( + &mut self, + window: &mut Window, + cx: &mut Context, + ) -> Option> { + let item = self.active_item()?; + self.remove_item(item.item_id(), false, false, window, cx); + Some(item) + } + pub fn remove_item( &mut self, item_id: EntityId, @@ -2222,7 +2247,19 @@ impl Pane { } pub fn split(&mut self, direction: SplitDirection, cx: &mut Context) { - cx.emit(Event::Split(direction)); + cx.emit(Event::Split { + direction, + clone_active_item: true, + }); + } + + pub fn split_and_move(&mut self, direction: SplitDirection, cx: &mut Context) { + if self.items.len() > 1 { + cx.emit(Event::Split { + direction, + clone_active_item: false, + }); + } } pub fn toolbar(&self) -> &Entity { @@ -3566,6 +3603,18 @@ impl Render for Pane { .on_action( cx.listener(|pane, _: &SplitDown, _, cx| pane.split(SplitDirection::Down, cx)), ) + .on_action(cx.listener(|pane, _: &SplitAndMoveUp, _, cx| { + pane.split_and_move(SplitDirection::Up, cx) + })) + .on_action(cx.listener(|pane, _: &SplitAndMoveDown, _, cx| { + pane.split_and_move(SplitDirection::Down, cx) + })) + .on_action(cx.listener(|pane, _: &SplitAndMoveLeft, _, cx| { + pane.split_and_move(SplitDirection::Left, cx) + })) + .on_action(cx.listener(|pane, _: &SplitAndMoveRight, _, cx| { + pane.split_and_move(SplitDirection::Right, cx) + })) .on_action(cx.listener(|_, _: &JoinIntoNext, _, cx| { cx.emit(Event::JoinIntoNext); })) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 49b00d26d4f14342c08a3361d8420a658ee1b5a8..e6a30e92d977ecf4781f48ce92f5f7483fe810b2 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3929,8 +3929,15 @@ impl Workspace { item: item.boxed_clone(), }); } - pane::Event::Split(direction) => { - self.split_and_clone(pane.clone(), *direction, window, cx); + pane::Event::Split { + direction, + clone_active_item, + } => { + if *clone_active_item { + self.split_and_clone(pane.clone(), *direction, window, cx); + } else { + self.split_and_move(pane.clone(), *direction, window, cx); + } } pane::Event::JoinIntoNext => { self.join_pane_into_next(pane.clone(), window, cx); @@ -4044,6 +4051,24 @@ impl Workspace { new_pane } + pub fn split_and_move( + &mut self, + pane: Entity, + direction: SplitDirection, + window: &mut Window, + cx: &mut Context, + ) { + let Some(item) = pane.update(cx, |pane, cx| pane.take_active_item(window, cx)) else { + return; + }; + let new_pane = self.add_pane(window, cx); + new_pane.update(cx, |pane, cx| { + pane.add_item(item, true, true, None, window, cx) + }); + self.center.split(&pane, &new_pane, direction).unwrap(); + cx.notify(); + } + pub fn split_and_clone( &mut self, pane: Entity,