@@ -37,6 +37,24 @@ use util::ResultExt;
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivateItem(pub usize);
+#[derive(Clone, PartialEq)]
+pub struct CloseItemById {
+ pub item_id: usize,
+ pub pane: WeakViewHandle<Pane>,
+}
+
+#[derive(Clone, PartialEq)]
+pub struct CloseItemsToTheLeftById {
+ pub item_id: usize,
+ pub pane: WeakViewHandle<Pane>,
+}
+
+#[derive(Clone, PartialEq)]
+pub struct CloseItemsToTheRightById {
+ pub item_id: usize,
+ pub pane: WeakViewHandle<Pane>,
+}
+
actions!(
pane,
[
@@ -57,12 +75,6 @@ actions!(
]
);
-#[derive(Clone, PartialEq)]
-pub struct CloseItem {
- pub item_id: usize,
- pub pane: WeakViewHandle<Pane>,
-}
-
#[derive(Clone, PartialEq)]
pub struct MoveItem {
pub item_id: usize,
@@ -92,11 +104,21 @@ pub struct DeployDockMenu;
#[derive(Clone, PartialEq)]
pub struct DeployNewMenu;
+#[derive(Clone, PartialEq)]
+pub struct DeployTabContextMenu {
+ pub position: Vector2F,
+ pub item_id: usize,
+ pub pane: WeakViewHandle<Pane>,
+}
+
impl_actions!(pane, [GoBack, GoForward, ActivateItem]);
impl_internal_actions!(
pane,
[
- CloseItem,
+ CloseItemById,
+ CloseItemsToTheLeftById,
+ CloseItemsToTheRightById,
+ DeployTabContextMenu,
DeploySplitMenu,
DeployNewMenu,
DeployDockMenu,
@@ -127,14 +149,34 @@ pub fn init(cx: &mut AppContext) {
cx.add_async_action(Pane::close_items_to_the_left);
cx.add_async_action(Pane::close_items_to_the_right);
cx.add_async_action(Pane::close_all_items);
- cx.add_async_action(|workspace: &mut Workspace, action: &CloseItem, cx| {
+ cx.add_async_action(|workspace: &mut Workspace, action: &CloseItemById, cx| {
let pane = action.pane.upgrade(cx)?;
- let task = Pane::close_item(workspace, pane, action.item_id, cx);
+ let task = Pane::close_item_by_id(workspace, pane, action.item_id, cx);
Some(cx.foreground().spawn(async move {
task.await?;
Ok(())
}))
});
+ cx.add_async_action(
+ |workspace: &mut Workspace, action: &CloseItemsToTheLeftById, cx| {
+ let pane = action.pane.upgrade(cx)?;
+ let task = Pane::close_items_to_the_left_by_id(workspace, pane, action.item_id, cx);
+ Some(cx.foreground().spawn(async move {
+ task.await?;
+ Ok(())
+ }))
+ },
+ );
+ cx.add_async_action(
+ |workspace: &mut Workspace, action: &CloseItemsToTheRightById, cx| {
+ let pane = action.pane.upgrade(cx)?;
+ let task = Pane::close_items_to_the_right_by_id(workspace, pane, action.item_id, cx);
+ Some(cx.foreground().spawn(async move {
+ task.await?;
+ Ok(())
+ }))
+ },
+ );
cx.add_action(
|workspace,
MoveItem {
@@ -168,6 +210,7 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Pane::deploy_split_menu);
cx.add_action(Pane::deploy_dock_menu);
cx.add_action(Pane::deploy_new_menu);
+ cx.add_action(Pane::deploy_tab_context_menu);
cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
Pane::reopen_closed_item(workspace, cx).detach();
});
@@ -214,6 +257,7 @@ pub struct Pane {
nav_history: Rc<RefCell<NavHistory>>,
toolbar: ViewHandle<Toolbar>,
tab_bar_context_menu: TabBarContextMenu,
+ tab_context_menu: ViewHandle<ContextMenu>,
docked: Option<DockAnchor>,
_background_actions: BackgroundActions,
_workspace_id: usize,
@@ -319,6 +363,7 @@ impl Pane {
kind: TabBarContextMenuKind::New,
handle: context_menu,
},
+ tab_context_menu: cx.add_view(ContextMenu::new),
docked,
_background_actions: background_actions,
_workspace_id: workspace_id,
@@ -742,9 +787,7 @@ impl Pane {
let pane = pane_handle.read(cx);
let active_item_id = pane.items[pane.active_item_index].id();
- let task = Self::close_items(workspace, pane_handle, cx, move |item_id| {
- item_id == active_item_id
- });
+ let task = Self::close_item_by_id(workspace, pane_handle, active_item_id, cx);
Some(cx.foreground().spawn(async move {
task.await?;
@@ -752,6 +795,17 @@ impl Pane {
}))
}
+ pub fn close_item_by_id(
+ workspace: &mut Workspace,
+ pane: ViewHandle<Pane>,
+ item_id_to_close: usize,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Task<Result<()>> {
+ Self::close_items(workspace, pane, cx, move |view_id| {
+ view_id == item_id_to_close
+ })
+ }
+
pub fn close_inactive_items(
workspace: &mut Workspace,
_: &CloseInactiveItems,
@@ -804,20 +858,35 @@ impl Pane {
let pane = pane_handle.read(cx);
let active_item_id = pane.items[pane.active_item_index].id();
+ let task = Self::close_items_to_the_left_by_id(workspace, pane_handle, active_item_id, cx);
+
+ Some(cx.foreground().spawn(async move {
+ task.await?;
+ Ok(())
+ }))
+ }
+
+ pub fn close_items_to_the_left_by_id(
+ workspace: &mut Workspace,
+ pane: ViewHandle<Pane>,
+ item_id: usize,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Task<Result<()>> {
let item_ids: Vec<_> = pane
+ .read(cx)
.items()
- .take_while(|item| item.id() != active_item_id)
+ .take_while(|item| item.id() != item_id)
.map(|item| item.id())
.collect();
- let task = Self::close_items(workspace, pane_handle, cx, move |item_id| {
+ let task = Self::close_items(workspace, pane, cx, move |item_id| {
item_ids.contains(&item_id)
});
- Some(cx.foreground().spawn(async move {
+ cx.foreground().spawn(async move {
task.await?;
Ok(())
- }))
+ })
}
pub fn close_items_to_the_right(
@@ -829,21 +898,36 @@ impl Pane {
let pane = pane_handle.read(cx);
let active_item_id = pane.items[pane.active_item_index].id();
+ let task = Self::close_items_to_the_right_by_id(workspace, pane_handle, active_item_id, cx);
+
+ Some(cx.foreground().spawn(async move {
+ task.await?;
+ Ok(())
+ }))
+ }
+
+ pub fn close_items_to_the_right_by_id(
+ workspace: &mut Workspace,
+ pane: ViewHandle<Pane>,
+ item_id: usize,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Task<Result<()>> {
let item_ids: Vec<_> = pane
+ .read(cx)
.items()
.rev()
- .take_while(|item| item.id() != active_item_id)
+ .take_while(|item| item.id() != item_id)
.map(|item| item.id())
.collect();
- let task = Self::close_items(workspace, pane_handle, cx, move |item_id| {
+ let task = Self::close_items(workspace, pane, cx, move |item_id| {
item_ids.contains(&item_id)
});
- Some(cx.foreground().spawn(async move {
+ cx.foreground().spawn(async move {
task.await?;
Ok(())
- }))
+ })
}
pub fn close_all_items(
@@ -861,17 +945,6 @@ impl Pane {
}))
}
- pub fn close_item(
- workspace: &mut Workspace,
- pane: ViewHandle<Pane>,
- item_id_to_close: usize,
- cx: &mut ViewContext<Workspace>,
- ) -> Task<Result<()>> {
- Self::close_items(workspace, pane, cx, move |view_id| {
- view_id == item_id_to_close
- })
- }
-
pub fn close_items(
workspace: &mut Workspace,
pane: ViewHandle<Pane>,
@@ -1207,6 +1280,65 @@ impl Pane {
self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
}
+ fn deploy_tab_context_menu(
+ &mut self,
+ action: &DeployTabContextMenu,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let target_item_id = action.item_id;
+ let target_pane = action.pane.clone();
+ let active_item_id = self.items[self.active_item_index].id();
+ let is_active_item = target_item_id == active_item_id;
+
+ let mut options = Vec::new();
+
+ // TODO: Explain why we are doing this - for the key bindings
+ options.push(if is_active_item {
+ ContextMenuItem::item("Close Active Item", CloseActiveItem)
+ } else {
+ ContextMenuItem::item(
+ "Close Inactive Item",
+ CloseItemById {
+ item_id: target_item_id,
+ pane: target_pane.clone(),
+ },
+ )
+ });
+ // This should really be called "close others" and the behaviour should be dynamically based on the tab the action is ran on. Currenlty, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
+ options.push(ContextMenuItem::item(
+ "Close Inactive Items",
+ CloseInactiveItems,
+ ));
+ options.push(ContextMenuItem::item("Close Clean Items", CloseCleanItems));
+ options.push(if is_active_item {
+ ContextMenuItem::item("Close Items To The Left", CloseItemsToTheLeft)
+ } else {
+ ContextMenuItem::item(
+ "Close Items To The Left",
+ CloseItemsToTheLeftById {
+ item_id: target_item_id,
+ pane: target_pane.clone(),
+ },
+ )
+ });
+ options.push(if is_active_item {
+ ContextMenuItem::item("Close Items To The Right", CloseItemsToTheRight)
+ } else {
+ ContextMenuItem::item(
+ "Close Items To The Right",
+ CloseItemsToTheRightById {
+ item_id: target_item_id,
+ pane: target_pane.clone(),
+ },
+ )
+ });
+ options.push(ContextMenuItem::item("Close All Items", CloseAllItems));
+
+ self.tab_context_menu.update(cx, |menu, cx| {
+ menu.show(action.position, AnchorCorner::TopLeft, options, cx);
+ });
+ }
+
pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
&self.toolbar
}
@@ -1277,13 +1409,22 @@ impl Pane {
})
.on_click(MouseButton::Middle, {
let item = item.clone();
+ let pane = pane.clone();
move |_, cx: &mut EventContext| {
- cx.dispatch_action(CloseItem {
+ cx.dispatch_action(CloseItemById {
item_id: item.id(),
pane: pane.clone(),
})
}
})
+ .on_down(MouseButton::Right, move |e, cx| {
+ let item = item.clone();
+ cx.dispatch_action(DeployTabContextMenu {
+ position: e.position,
+ item_id: item.id(),
+ pane: pane.clone(),
+ });
+ })
.boxed()
}
});
@@ -1454,7 +1595,7 @@ impl Pane {
.on_click(MouseButton::Left, {
let pane = pane.clone();
move |_, cx| {
- cx.dispatch_action(CloseItem {
+ cx.dispatch_action(CloseItemById {
item_id,
pane: pane.clone(),
})
@@ -1624,6 +1765,7 @@ impl View for Pane {
.flex(1., true)
.boxed()
})
+ .with_child(ChildView::new(&self.tab_context_menu, cx).boxed())
.boxed()
} else {
enum EmptyPane {}
@@ -2219,14 +2361,14 @@ mod tests {
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
- add_labled_item(&workspace, &pane, "A", cx);
- add_labled_item(&workspace, &pane, "B", cx);
- add_labled_item(&workspace, &pane, "C", cx);
- add_labled_item(&workspace, &pane, "D", cx);
+ add_labeled_item(&workspace, &pane, "A", false, cx);
+ add_labeled_item(&workspace, &pane, "B", false, cx);
+ add_labeled_item(&workspace, &pane, "C", false, cx);
+ add_labeled_item(&workspace, &pane, "D", false, cx);
assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
- add_labled_item(&workspace, &pane, "1", cx);
+ add_labeled_item(&workspace, &pane, "1", false, cx);
assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
workspace.update(cx, |workspace, cx| {
@@ -2257,14 +2399,125 @@ mod tests {
assert_item_labels(&pane, ["A*"], cx);
}
- fn add_labled_item(
+ #[gpui::test]
+ async fn test_close_inactive_items(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+ Settings::test_async(cx);
+ let fs = FakeFs::new(cx.background());
+
+ let project = Project::test(fs, None, cx).await;
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+ set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx);
+
+ workspace.update(cx, |workspace, cx| {
+ Pane::close_inactive_items(workspace, &CloseInactiveItems, cx);
+ });
+
+ deterministic.run_until_parked();
+ assert_item_labels(&pane, ["C*"], cx);
+ }
+
+ #[gpui::test]
+ async fn test_close_clean_items(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+ Settings::test_async(cx);
+ let fs = FakeFs::new(cx.background());
+
+ let project = Project::test(fs, None, cx).await;
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+ add_labeled_item(&workspace, &pane, "A", true, cx);
+ add_labeled_item(&workspace, &pane, "B", false, cx);
+ add_labeled_item(&workspace, &pane, "C", false, cx);
+ add_labeled_item(&workspace, &pane, "D", false, cx);
+ add_labeled_item(&workspace, &pane, "E", false, cx);
+ assert_item_labels(&pane, ["A", "B", "C", "D", "E*"], cx);
+
+ workspace.update(cx, |workspace, cx| {
+ Pane::close_clean_items(workspace, &CloseCleanItems, cx);
+ });
+
+ deterministic.run_until_parked();
+ assert_item_labels(&pane, ["A*"], cx);
+ }
+
+ #[gpui::test]
+ async fn test_close_items_to_the_left(
+ deterministic: Arc<Deterministic>,
+ cx: &mut TestAppContext,
+ ) {
+ Settings::test_async(cx);
+ let fs = FakeFs::new(cx.background());
+
+ let project = Project::test(fs, None, cx).await;
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+ set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx);
+
+ workspace.update(cx, |workspace, cx| {
+ Pane::close_items_to_the_left(workspace, &CloseItemsToTheLeft, cx);
+ });
+
+ deterministic.run_until_parked();
+ assert_item_labels(&pane, ["C*", "D", "E"], cx);
+ }
+
+ #[gpui::test]
+ async fn test_close_items_to_the_right(
+ deterministic: Arc<Deterministic>,
+ cx: &mut TestAppContext,
+ ) {
+ Settings::test_async(cx);
+ let fs = FakeFs::new(cx.background());
+
+ let project = Project::test(fs, None, cx).await;
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+ set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx);
+
+ workspace.update(cx, |workspace, cx| {
+ Pane::close_items_to_the_right(workspace, &CloseItemsToTheRight, cx);
+ });
+
+ deterministic.run_until_parked();
+ assert_item_labels(&pane, ["A", "B", "C*"], cx);
+ }
+
+ #[gpui::test]
+ async fn test_close_all_items(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+ Settings::test_async(cx);
+ let fs = FakeFs::new(cx.background());
+
+ let project = Project::test(fs, None, cx).await;
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+ add_labeled_item(&workspace, &pane, "A", false, cx);
+ add_labeled_item(&workspace, &pane, "B", false, cx);
+ add_labeled_item(&workspace, &pane, "C", false, cx);
+ assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+ workspace.update(cx, |workspace, cx| {
+ Pane::close_all_items(workspace, &CloseAllItems, cx);
+ });
+
+ deterministic.run_until_parked();
+ assert_item_labels(&pane, [], cx);
+ }
+
+ fn add_labeled_item(
workspace: &ViewHandle<Workspace>,
pane: &ViewHandle<Pane>,
label: &str,
+ is_dirty: bool,
cx: &mut TestAppContext,
) -> Box<ViewHandle<TestItem>> {
workspace.update(cx, |workspace, cx| {
- let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
+ let labeled_item =
+ Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
Pane::add_item(
workspace,