Allow SplitAndMove on panes (#38034)

Conrad Irwin created

Updates #19350

Release Notes:

- Add `pane::SplitAndMove{Up,Down,Left,Right}` to allow creating a split
without cloning the current buffer.

Change summary

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(-)

Detailed changes

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();

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<dyn ItemHandle>,
     },
-    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<Self>,
+    ) -> Option<Box<dyn ItemHandle>> {
+        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<Self>) {
-        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<Self>) {
+        if self.items.len() > 1 {
+            cx.emit(Event::Split {
+                direction,
+                clone_active_item: false,
+            });
+        }
     }
 
     pub fn toolbar(&self) -> &Entity<Toolbar> {
@@ -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);
             }))

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<Pane>,
+        direction: SplitDirection,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        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<Pane>,