Add a way to join all panes into one (#17673)
Closes https://github.com/zed-industries/zed/issues/17536 Closes https://github.com/zed-industries/zed/pull/17548 Release Notes: - Added a way to join all panes into one with `pane::JoinAll` action ([#17536](https://github.com/zed-industries/zed/issues/17536)) --------- Co-authored-by: Yogesh Dhamija <ydhamija96@gmail.com>
This commit is contained in:
parent
331d28d479
commit
ec189fe884
2 changed files with 173 additions and 47 deletions
|
@ -3,6 +3,7 @@ use crate::{
|
||||||
ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings, TabContentParams,
|
ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings, TabContentParams,
|
||||||
WeakItemHandle,
|
WeakItemHandle,
|
||||||
},
|
},
|
||||||
|
move_item,
|
||||||
notifications::NotifyResultExt,
|
notifications::NotifyResultExt,
|
||||||
toolbar::Toolbar,
|
toolbar::Toolbar,
|
||||||
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
|
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
|
||||||
|
@ -149,6 +150,7 @@ actions!(
|
||||||
GoBack,
|
GoBack,
|
||||||
GoForward,
|
GoForward,
|
||||||
JoinIntoNext,
|
JoinIntoNext,
|
||||||
|
JoinAll,
|
||||||
ReopenClosedItem,
|
ReopenClosedItem,
|
||||||
SplitLeft,
|
SplitLeft,
|
||||||
SplitUp,
|
SplitUp,
|
||||||
|
@ -188,6 +190,7 @@ pub enum Event {
|
||||||
item_id: EntityId,
|
item_id: EntityId,
|
||||||
},
|
},
|
||||||
Split(SplitDirection),
|
Split(SplitDirection),
|
||||||
|
JoinAll,
|
||||||
JoinIntoNext,
|
JoinIntoNext,
|
||||||
ChangeItemTitle,
|
ChangeItemTitle,
|
||||||
Focus,
|
Focus,
|
||||||
|
@ -220,6 +223,7 @@ impl fmt::Debug for Event {
|
||||||
.debug_struct("Split")
|
.debug_struct("Split")
|
||||||
.field("direction", direction)
|
.field("direction", direction)
|
||||||
.finish(),
|
.finish(),
|
||||||
|
Event::JoinAll => f.write_str("JoinAll"),
|
||||||
Event::JoinIntoNext => f.write_str("JoinIntoNext"),
|
Event::JoinIntoNext => f.write_str("JoinIntoNext"),
|
||||||
Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),
|
Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),
|
||||||
Event::Focus => f.write_str("Focus"),
|
Event::Focus => f.write_str("Focus"),
|
||||||
|
@ -679,6 +683,10 @@ impl Pane {
|
||||||
cx.emit(Event::JoinIntoNext);
|
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>) {
|
fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.toolbar.update(cx, |_, cx| cx.notify());
|
self.toolbar.update(cx, |_, cx| cx.notify());
|
||||||
}
|
}
|
||||||
|
@ -1757,9 +1765,7 @@ impl Pane {
|
||||||
|
|
||||||
self.workspace
|
self.workspace
|
||||||
.update(cx, |_, cx| {
|
.update(cx, |_, cx| {
|
||||||
cx.defer(move |this, cx| {
|
cx.defer(move |_, cx| move_item(&pane, &pane, id, destination_index, cx));
|
||||||
this.move_item(pane.clone(), pane, id, destination_index, cx)
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
|
@ -1777,9 +1783,7 @@ impl Pane {
|
||||||
|
|
||||||
self.workspace
|
self.workspace
|
||||||
.update(cx, |_, cx| {
|
.update(cx, |_, cx| {
|
||||||
cx.defer(move |this, cx| {
|
cx.defer(move |_, cx| move_item(&pane, &pane, id, destination_index, cx));
|
||||||
this.move_item(pane.clone(), pane, id, destination_index, cx)
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.ok()?;
|
.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();
|
.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, _: &GoBack, cx| pane.navigate_backward(cx)))
|
||||||
.on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(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, _: &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::toggle_zoom))
|
||||||
.on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
|
.on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
|
||||||
pane.activate_item(action.0, true, true, cx);
|
pane.activate_item(action.0, true, true, cx);
|
||||||
|
|
|
@ -2965,6 +2965,7 @@ impl Workspace {
|
||||||
self.split_and_clone(pane, *direction, cx);
|
self.split_and_clone(pane, *direction, cx);
|
||||||
}
|
}
|
||||||
pane::Event::JoinIntoNext => self.join_pane_into_next(pane, 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 } => {
|
pane::Event::Remove { focus_on_pane } => {
|
||||||
self.remove_pane(pane, focus_on_pane.clone(), cx)
|
self.remove_pane(pane, focus_on_pane.clone(), cx)
|
||||||
}
|
}
|
||||||
|
@ -3094,7 +3095,7 @@ impl Workspace {
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_pane = self.add_pane(cx);
|
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
|
self.center
|
||||||
.split(&pane_to_split, &new_pane, split_direction)
|
.split(&pane_to_split, &new_pane, split_direction)
|
||||||
.unwrap();
|
.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>) {
|
pub fn join_pane_into_next(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
|
||||||
let next_pane = self
|
let next_pane = self
|
||||||
.find_pane_in_direction(SplitDirection::Right, cx)
|
.find_pane_in_direction(SplitDirection::Right, cx)
|
||||||
|
@ -3131,48 +3143,10 @@ impl Workspace {
|
||||||
let Some(next_pane) = next_pane else {
|
let Some(next_pane) = next_pane else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
move_all_items(&pane, &next_pane, cx);
|
||||||
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);
|
|
||||||
}
|
|
||||||
cx.notify();
|
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(
|
fn remove_pane(
|
||||||
&mut self,
|
&mut self,
|
||||||
pane: View<Pane>,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{cell::RefCell, rc::Rc};
|
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);
|
struct TestModal(FocusHandle);
|
||||||
|
|
||||||
impl TestModal {
|
impl TestModal {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue