@@ -3,6 +3,7 @@ use crate::{
ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings, TabContentParams,
WeakItemHandle,
},
+ move_item,
notifications::NotifyResultExt,
toolbar::Toolbar,
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
@@ -149,6 +150,7 @@ actions!(
GoBack,
GoForward,
JoinIntoNext,
+ JoinAll,
ReopenClosedItem,
SplitLeft,
SplitUp,
@@ -188,6 +190,7 @@ pub enum Event {
item_id: EntityId,
},
Split(SplitDirection),
+ JoinAll,
JoinIntoNext,
ChangeItemTitle,
Focus,
@@ -220,6 +223,7 @@ impl fmt::Debug for Event {
.debug_struct("Split")
.field("direction", direction)
.finish(),
+ Event::JoinAll => f.write_str("JoinAll"),
Event::JoinIntoNext => f.write_str("JoinIntoNext"),
Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),
Event::Focus => f.write_str("Focus"),
@@ -679,6 +683,10 @@ impl Pane {
cx.emit(Event::JoinIntoNext);
}
+ fn join_all(&mut self, cx: &mut ViewContext<Self>) {
+ cx.emit(Event::JoinAll);
+ }
+
fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
self.toolbar.update(cx, |_, cx| cx.notify());
}
@@ -1757,9 +1765,7 @@ impl Pane {
self.workspace
.update(cx, |_, cx| {
- cx.defer(move |this, cx| {
- this.move_item(pane.clone(), pane, id, destination_index, cx)
- });
+ cx.defer(move |_, cx| move_item(&pane, &pane, id, destination_index, cx));
})
.ok()?;
@@ -1777,9 +1783,7 @@ impl Pane {
self.workspace
.update(cx, |_, cx| {
- cx.defer(move |this, cx| {
- this.move_item(pane.clone(), pane, id, destination_index, cx)
- });
+ cx.defer(move |_, cx| move_item(&pane, &pane, id, destination_index, cx));
})
.ok()?;
@@ -2349,7 +2353,7 @@ impl Pane {
}
})
}
- workspace.move_item(from_pane.clone(), to_pane.clone(), item_id, ix, cx);
+ move_item(&from_pane, &to_pane, item_id, ix, cx);
});
})
.log_err();
@@ -2556,6 +2560,7 @@ impl Render for Pane {
.on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx)))
.on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx)))
.on_action(cx.listener(|pane, _: &JoinIntoNext, cx| pane.join_into_next(cx)))
+ .on_action(cx.listener(|pane, _: &JoinAll, cx| pane.join_all(cx)))
.on_action(cx.listener(Pane::toggle_zoom))
.on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
pane.activate_item(action.0, true, true, cx);
@@ -2965,6 +2965,7 @@ impl Workspace {
self.split_and_clone(pane, *direction, cx);
}
pane::Event::JoinIntoNext => self.join_pane_into_next(pane, cx),
+ pane::Event::JoinAll => self.join_all_panes(cx),
pane::Event::Remove { focus_on_pane } => {
self.remove_pane(pane, focus_on_pane.clone(), cx)
}
@@ -3094,7 +3095,7 @@ impl Workspace {
};
let new_pane = self.add_pane(cx);
- self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
+ move_item(&from, &new_pane, item_id_to_move, 0, cx);
self.center
.split(&pane_to_split, &new_pane, split_direction)
.unwrap();
@@ -3122,6 +3123,17 @@ impl Workspace {
}))
}
+ pub fn join_all_panes(&mut self, cx: &mut ViewContext<Self>) {
+ let active_item = self.active_pane.read(cx).active_item();
+ for pane in &self.panes {
+ join_pane_into_active(&self.active_pane, pane, cx);
+ }
+ if let Some(active_item) = active_item {
+ self.activate_item(active_item.as_ref(), true, true, cx);
+ }
+ cx.notify();
+ }
+
pub fn join_pane_into_next(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
let next_pane = self
.find_pane_in_direction(SplitDirection::Right, cx)
@@ -3131,48 +3143,10 @@ impl Workspace {
let Some(next_pane) = next_pane else {
return;
};
-
- let item_ids: Vec<EntityId> = pane.read(cx).items().map(|item| item.item_id()).collect();
- for item_id in item_ids {
- self.move_item(pane.clone(), next_pane.clone(), item_id, 0, cx);
- }
+ move_all_items(&pane, &next_pane, cx);
cx.notify();
}
- pub fn move_item(
- &mut self,
- source: View<Pane>,
- destination: View<Pane>,
- item_id_to_move: EntityId,
- destination_index: usize,
- cx: &mut ViewContext<Self>,
- ) {
- let Some((item_ix, item_handle)) = source
- .read(cx)
- .items()
- .enumerate()
- .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
- else {
- // Tab was closed during drag
- return;
- };
-
- let item_handle = item_handle.clone();
-
- if source != destination {
- // Close item from previous pane
- source.update(cx, |source, cx| {
- source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), cx);
- });
- }
-
- // This automatically removes duplicate items in the pane
- destination.update(cx, |destination, cx| {
- destination.add_item(item_handle, true, true, Some(destination_index), cx);
- destination.focus(cx)
- });
- }
-
fn remove_pane(
&mut self,
pane: View<Pane>,
@@ -5944,6 +5918,79 @@ fn resize_edge(
}
}
+fn join_pane_into_active(active_pane: &View<Pane>, pane: &View<Pane>, cx: &mut WindowContext<'_>) {
+ if pane == active_pane {
+ return;
+ } else if pane.read(cx).items_len() == 0 {
+ pane.update(cx, |_, cx| {
+ cx.emit(pane::Event::Remove {
+ focus_on_pane: None,
+ });
+ })
+ } else {
+ move_all_items(pane, active_pane, cx);
+ }
+}
+
+fn move_all_items(from_pane: &View<Pane>, to_pane: &View<Pane>, cx: &mut WindowContext<'_>) {
+ let destination_is_different = from_pane != to_pane;
+ let mut moved_items = 0;
+ for (item_ix, item_handle) in from_pane
+ .read(cx)
+ .items()
+ .enumerate()
+ .map(|(ix, item)| (ix, item.clone()))
+ .collect::<Vec<_>>()
+ {
+ let ix = item_ix - moved_items;
+ if destination_is_different {
+ // Close item from previous pane
+ from_pane.update(cx, |source, cx| {
+ source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), cx);
+ });
+ moved_items += 1;
+ }
+
+ // This automatically removes duplicate items in the pane
+ to_pane.update(cx, |destination, cx| {
+ destination.add_item(item_handle, true, true, None, cx);
+ destination.focus(cx)
+ });
+ }
+}
+
+pub fn move_item(
+ source: &View<Pane>,
+ destination: &View<Pane>,
+ item_id_to_move: EntityId,
+ destination_index: usize,
+ cx: &mut WindowContext<'_>,
+) {
+ let Some((item_ix, item_handle)) = source
+ .read(cx)
+ .items()
+ .enumerate()
+ .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
+ .map(|(ix, item)| (ix, item.clone()))
+ else {
+ // Tab was closed during drag
+ return;
+ };
+
+ if source != destination {
+ // Close item from previous pane
+ source.update(cx, |source, cx| {
+ source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), cx);
+ });
+ }
+
+ // This automatically removes duplicate items in the pane
+ destination.update(cx, |destination, cx| {
+ destination.add_item(item_handle, true, true, Some(destination_index), cx);
+ destination.focus(cx)
+ });
+}
+
#[cfg(test)]
mod tests {
use std::{cell::RefCell, rc::Rc};
@@ -6855,6 +6902,80 @@ mod tests {
});
}
+ fn add_an_item_to_active_pane(
+ cx: &mut VisualTestContext,
+ workspace: &View<Workspace>,
+ item_id: u64,
+ ) -> View<TestItem> {
+ let item = cx.new_view(|cx| {
+ TestItem::new(cx).with_project_items(&[TestProjectItem::new(
+ item_id,
+ "item{item_id}.txt",
+ cx,
+ )])
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, cx);
+ });
+ return item;
+ }
+
+ fn split_pane(cx: &mut VisualTestContext, workspace: &View<Workspace>) -> View<Pane> {
+ return workspace.update(cx, |workspace, cx| {
+ let new_pane =
+ workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
+ new_pane
+ });
+ }
+
+ #[gpui::test]
+ async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
+ init_test(cx);
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, None, cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
+ add_an_item_to_active_pane(cx, &workspace, 1);
+ split_pane(cx, &workspace);
+ add_an_item_to_active_pane(cx, &workspace, 2);
+ split_pane(cx, &workspace); // empty pane
+ split_pane(cx, &workspace);
+ let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
+
+ cx.executor().run_until_parked();
+
+ workspace.update(cx, |workspace, cx| {
+ let num_panes = workspace.panes().len();
+ let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
+ let active_item = workspace
+ .active_pane()
+ .read(cx)
+ .active_item()
+ .expect("item is in focus");
+
+ assert_eq!(num_panes, 4);
+ assert_eq!(num_items_in_current_pane, 1);
+ assert_eq!(active_item.item_id(), last_item.item_id());
+ });
+
+ workspace.update(cx, |workspace, cx| {
+ workspace.join_all_panes(cx);
+ });
+
+ workspace.update(cx, |workspace, cx| {
+ let num_panes = workspace.panes().len();
+ let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
+ let active_item = workspace
+ .active_pane()
+ .read(cx)
+ .active_item()
+ .expect("item is in focus");
+
+ assert_eq!(num_panes, 1);
+ assert_eq!(num_items_in_current_pane, 3);
+ assert_eq!(active_item.item_id(), last_item.item_id());
+ });
+ }
struct TestModal(FocusHandle);
impl TestModal {