From 793381f4552378b5d8d65e8f73d8243acd372322 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Dec 2023 17:41:29 -0800 Subject: [PATCH 1/4] Render zoomed item in the workspace --- crates/workspace2/src/workspace2.rs | 49 ++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index a789809537d1bf5fdc530cf293d5f8b9d542c8b2..f2482d034c1de03950aba7d62639241f806c0eae 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3556,6 +3556,8 @@ impl Render for Workspace { ) }; + let theme = cx.theme().clone(); + let colors = theme.colors(); cx.set_rem_size(ui_font_size); self.actions(div(), cx) @@ -3568,10 +3570,10 @@ impl Render for Workspace { .gap_0() .justify_start() .items_start() - .text_color(cx.theme().colors().text) - .bg(cx.theme().colors().background) + .text_color(colors.text) + .bg(colors.background) .border() - .border_color(cx.theme().colors().border) + .border_color(colors.border) .children(self.titlebar_item.clone()) .child( div() @@ -3584,7 +3586,7 @@ impl Render for Workspace { .overflow_hidden() .border_t() .border_b() - .border_color(cx.theme().colors().border) + .border_color(colors.border) .child( canvas(cx.listener(|workspace, bounds, _| { workspace.bounds = *bounds; @@ -3623,13 +3625,17 @@ impl Render for Workspace { .flex_row() .h_full() // Left Dock - .child( - div() - .flex() - .flex_none() - .overflow_hidden() - .child(self.left_dock.clone()), - ) + .children(if self.zoomed_position == Some(DockPosition::Left) { + None + } else { + Some( + div() + .flex() + .flex_none() + .overflow_hidden() + .child(self.left_dock.clone()), + ) + }) // Panes .child( div() @@ -3657,7 +3663,26 @@ impl Render for Workspace { .child(self.right_dock.clone()), ), ) - .children(self.render_notifications(cx)), + .children(self.render_notifications(cx)) + .children(self.zoomed.as_ref().and_then(|view| { + let zoomed_view = view.upgrade()?; + let div = div() + .z_index(1) + .absolute() + .overflow_hidden() + .border_color(colors.border) + .bg(colors.background) + .child(zoomed_view) + .inset_0() + .shadow_lg(); + + Some(match self.zoomed_position { + Some(DockPosition::Left) => div.right_2().border_r(), + Some(DockPosition::Right) => div.left_2().border_l(), + Some(DockPosition::Bottom) => div.top_2().border_t(), + None => div.top_2().bottom_2().left_2().right_2().border(), + }) + })), ) .child(self.status_bar.clone()) } From a4c123bb1103d1953b6bbdee6ef001845069bee0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 15 Dec 2023 11:47:01 -0800 Subject: [PATCH 2/4] Fix logic for activating panel when re-docking it --- crates/workspace2/src/dock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index f76bb82177084f2e638e31e0e88bad8a3552a182..e44471a871ac416546657880964720f3ee638d3c 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -317,7 +317,7 @@ impl Dock { new_dock.add_panel(panel.clone(), workspace.clone(), cx); if was_visible { new_dock.set_open(true, cx); - new_dock.activate_panel(this.panels_len() - 1, cx); + new_dock.activate_panel(new_dock.panels_len() - 1, cx); } }); } From 9a8225c8553e7d6208fd1a962fc182f5f3eb3c89 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 15 Dec 2023 11:48:25 -0800 Subject: [PATCH 3/4] When a dock is zoomed, don't render it in its normal place --- crates/workspace2/src/workspace2.rs | 165 ++++------------------------ 1 file changed, 19 insertions(+), 146 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index f2482d034c1de03950aba7d62639241f806c0eae..866762e7d5e3cabf301decb43f32fb8e9a1c7645 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3625,17 +3625,15 @@ impl Render for Workspace { .flex_row() .h_full() // Left Dock - .children(if self.zoomed_position == Some(DockPosition::Left) { - None - } else { - Some( + .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then( + || { div() .flex() .flex_none() .overflow_hidden() - .child(self.left_dock.clone()), - ) - }) + .child(self.left_dock.clone()) + }, + )) // Panes .child( div() @@ -3652,16 +3650,22 @@ impl Render for Workspace { &self.app_state, cx, )) - .child(self.bottom_dock.clone()), + .children( + self.zoomed_position + .ne(&Some(DockPosition::Bottom)) + .then(|| self.bottom_dock.clone()), + ), ) // Right Dock - .child( - div() - .flex() - .flex_none() - .overflow_hidden() - .child(self.right_dock.clone()), - ), + .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then( + || { + div() + .flex() + .flex_none() + .overflow_hidden() + .child(self.right_dock.clone()) + }, + )), ) .children(self.render_notifications(cx)) .children(self.zoomed.as_ref().and_then(|view| { @@ -3688,137 +3692,6 @@ impl Render for Workspace { } } -// impl View for Workspace { - -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// let theme = theme::current(cx).clone(); -// Stack::new() -// .with_child( -// Flex::column() -// .with_child(self.render_titlebar(&theme, cx)) -// .with_child( -// Stack::new() -// .with_child({ -// let project = self.project.clone(); -// Flex::row() -// .with_children(self.render_dock(DockPosition::Left, cx)) -// .with_child( -// Flex::column() -// .with_child( -// FlexItem::new( -// self.center.render( -// &project, -// &theme, -// &self.follower_states, -// self.active_call(), -// self.active_pane(), -// self.zoomed -// .as_ref() -// .and_then(|zoomed| zoomed.upgrade(cx)) -// .as_ref(), -// &self.app_state, -// cx, -// ), -// ) -// .flex(1., true), -// ) -// .with_children( -// self.render_dock(DockPosition::Bottom, cx), -// ) -// .flex(1., true), -// ) -// .with_children(self.render_dock(DockPosition::Right, cx)) -// }) -// .with_child(Overlay::new( -// Stack::new() -// .with_children(self.zoomed.as_ref().and_then(|zoomed| { -// enum ZoomBackground {} -// let zoomed = zoomed.upgrade(cx)?; - -// let mut foreground_style = -// theme.workspace.zoomed_pane_foreground; -// if let Some(zoomed_dock_position) = self.zoomed_position { -// foreground_style = -// theme.workspace.zoomed_panel_foreground; -// let margin = foreground_style.margin.top; -// let border = foreground_style.border.top; - -// // Only include a margin and border on the opposite side. -// foreground_style.margin.top = 0.; -// foreground_style.margin.left = 0.; -// foreground_style.margin.bottom = 0.; -// foreground_style.margin.right = 0.; -// foreground_style.border.top = false; -// foreground_style.border.left = false; -// foreground_style.border.bottom = false; -// foreground_style.border.right = false; -// match zoomed_dock_position { -// DockPosition::Left => { -// foreground_style.margin.right = margin; -// foreground_style.border.right = border; -// } -// DockPosition::Right => { -// foreground_style.margin.left = margin; -// foreground_style.border.left = border; -// } -// DockPosition::Bottom => { -// foreground_style.margin.top = margin; -// foreground_style.border.top = border; -// } -// } -// } - -// Some( -// ChildView::new(&zoomed, cx) -// .contained() -// .with_style(foreground_style) -// .aligned() -// .contained() -// .with_style(theme.workspace.zoomed_background) -// .mouse::(0) -// .capture_all() -// .on_down( -// MouseButton::Left, -// |_, this: &mut Self, cx| { -// this.zoom_out(cx); -// }, -// ), -// ) -// })) -// .with_children(self.modal.as_ref().map(|modal| { -// // Prevent clicks within the modal from falling -// // through to the rest of the workspace. -// enum ModalBackground {} -// MouseEventHandler::new::( -// 0, -// cx, -// |_, cx| ChildView::new(modal.view.as_any(), cx), -// ) -// .on_click(MouseButton::Left, |_, _, _| {}) -// .contained() -// .with_style(theme.workspace.modal) -// .aligned() -// .top() -// })) -// .with_children(self.render_notifications(&theme.workspace, cx)), -// )) -// .provide_resize_bounds::() -// .flex(1.0, true), -// ) -// .with_child(ChildView::new(&self.status_bar, cx)) -// .contained() -// .with_background_color(theme.workspace.background), -// ) -// .with_children(DragAndDrop::render(cx)) -// .with_children(self.render_disconnected_overlay(cx)) -// .into_any_named("workspace") -// } - -// fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext) -> bool { -// DragAndDrop::::update_modifiers(e.modifiers, cx) -// } -// } - impl WorkspaceStore { pub fn new(client: Arc, cx: &mut ModelContext) -> Self { Self { From 4c3ec0a8e553dbe7afd5779e805a75283ced0597 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 15 Dec 2023 12:00:58 -0800 Subject: [PATCH 4/4] Enable pane tests --- crates/workspace2/src/pane.rs | 1187 +++++++++++++++------------------ 1 file changed, 535 insertions(+), 652 deletions(-) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 11588212ef76e3891c57fc5a14778fd8e48a9e37..e5b4125447668fb1246821f2b4d5d78c8a6d438f 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -177,7 +177,7 @@ pub struct Pane { toolbar: View, new_item_menu: Option>, split_item_menu: Option>, - // tab_context_menu: ViewHandle, + // tab_context_menu: View, workspace: WeakView, project: Model, drag_split_direction: Option, @@ -256,11 +256,11 @@ struct DraggedTab { // struct TabBarContextMenu { // kind: TabBarContextMenuKind, -// handle: ViewHandle, +// handle: View, // } // impl TabBarContextMenu { -// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { +// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { // if self.kind == kind { // return Some(self.handle.clone()); // } @@ -329,7 +329,7 @@ impl Pane { ) -> Self { // todo!("context menu") // let pane_view_id = cx.view_id(); - // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)); + // let context_menu = cx.build_view(|cx| ContextMenu::new(pane_view_id, cx)); // context_menu.update(cx, |menu, _| { // menu.set_position_mode(OverlayPositionMode::Local) // }); @@ -368,7 +368,7 @@ impl Pane { // kind: TabBarContextMenuKind::New, // handle: context_menu, // }, - // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), + // tab_context_menu: cx.build_view(|_| ContextMenu::new(pane_view_id, cx)), workspace, project, // can_drop: Rc::new(|_, _| true), @@ -1968,52 +1968,6 @@ impl Render for Pane { }), ) } - - // fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { - // if !self.has_focus { - // self.has_focus = true; - // cx.emit(Event::Focus); - // cx.notify(); - // } - - // self.toolbar.update(cx, |toolbar, cx| { - // toolbar.focus_changed(true, cx); - // }); - - // if let Some(active_item) = self.active_item() { - // if cx.is_self_focused() { - // // Pane was focused directly. We need to either focus a view inside the active item, - // // or focus the active item itself - // if let Some(weak_last_focused_view) = - // self.last_focused_view_by_item.get(&active_item.id()) - // { - // if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) { - // cx.focus(&last_focused_view); - // return; - // } else { - // self.last_focused_view_by_item.remove(&active_item.id()); - // } - // } - - // cx.focus(active_item.as_any()); - // } else if focused != self.tab_bar_context_menu.handle { - // self.last_focused_view_by_item - // .insert(active_item.id(), focused.downgrade()); - // } - // } - // } - - // fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - // self.has_focus = false; - // self.toolbar.update(cx, |toolbar, cx| { - // toolbar.focus_changed(false, cx); - // }); - // cx.notify(); - // } - - // fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { - // Self::reset_to_default_keymap_context(keymap); - // } } impl ItemNavHistory { @@ -2171,98 +2125,6 @@ impl NavHistoryState { } } -// pub struct PaneBackdrop { -// child_view: usize, -// child: AnyElement, -// } - -// impl PaneBackdrop { -// pub fn new(pane_item_view: usize, child: AnyElement) -> Self { -// PaneBackdrop { -// child, -// child_view: pane_item_view, -// } -// } -// } - -// impl Element for PaneBackdrop { -// type LayoutState = (); - -// type PaintState = (); - -// fn layout( -// &mut self, -// constraint: gpui::SizeConstraint, -// view: &mut V, -// cx: &mut ViewContext, -// ) -> (Vector2F, Self::LayoutState) { -// let size = self.child.layout(constraint, view, cx); -// (size, ()) -// } - -// fn paint( -// &mut self, -// bounds: RectF, -// visible_bounds: RectF, -// _: &mut Self::LayoutState, -// view: &mut V, -// cx: &mut ViewContext, -// ) -> Self::PaintState { -// let background = theme::current(cx).editor.background; - -// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - -// cx.scene().push_quad(gpui::Quad { -// bounds: RectF::new(bounds.origin(), bounds.size()), -// background: Some(background), -// ..Default::default() -// }); - -// let child_view_id = self.child_view; -// cx.scene().push_mouse_region( -// MouseRegion::new::(child_view_id, 0, visible_bounds).on_down( -// gpui::platform::MouseButton::Left, -// move |_, _: &mut V, cx| { -// let window = cx.window(); -// cx.app_context().focus(window, Some(child_view_id)) -// }, -// ), -// ); - -// cx.scene().push_layer(Some(bounds)); -// self.child.paint(bounds.origin(), visible_bounds, view, cx); -// cx.scene().pop_layer(); -// } - -// fn rect_for_text_range( -// &self, -// range_utf16: std::ops::Range, -// _bounds: RectF, -// _visible_bounds: RectF, -// _layout: &Self::LayoutState, -// _paint: &Self::PaintState, -// view: &V, -// cx: &gpui::ViewContext, -// ) -> Option { -// self.child.rect_for_text_range(range_utf16, view, cx) -// } - -// fn debug( -// &self, -// _bounds: RectF, -// _layout: &Self::LayoutState, -// _paint: &Self::PaintState, -// view: &V, -// cx: &gpui::ViewContext, -// ) -> serde_json::Value { -// gpui::json::json!({ -// "type": "Pane Back Drop", -// "view": self.child_view, -// "child": self.child.debug(view, cx), -// }) -// } -// } - fn dirty_message_for(buffer_path: Option) -> String { let path = buffer_path .as_ref() @@ -2272,528 +2134,549 @@ fn dirty_message_for(buffer_path: Option) -> String { format!("{path} contains unsaved edits. Do you want to save it?") } -// todo!("uncomment tests") -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::item::test::{TestItem, TestProjectItem}; -// use gpui::TestAppContext; -// use project::FakeFs; -// use settings::SettingsStore; - -// #[gpui::test] -// async fn test_remove_active_empty(cx: &mut TestAppContext) { -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// pane.update(cx, |pane, cx| { -// assert!(pane -// .close_active_item(&CloseActiveItem { save_intent: None }, cx) -// .is_none()) -// }); -// } +#[cfg(test)] +mod tests { + use super::*; + use crate::item::test::{TestItem, TestProjectItem}; + use gpui::{TestAppContext, VisualTestContext}; + use project::FakeFs; + use settings::SettingsStore; + use theme::LoadThemes; + + #[gpui::test] + async fn test_remove_active_empty(cx: &mut 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.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + pane.update(cx, |pane, cx| { + assert!(pane + .close_active_item(&CloseActiveItem { save_intent: None }, cx) + .is_none()) + }); + } -// #[gpui::test] -// async fn test_add_item_with_new_item(cx: &mut TestAppContext) { -// cx.foreground().forbid_parking(); -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// // 1. Add with a destination index -// // a. Add before the active item -// set_labeled_items(&pane, ["A", "B*", "C"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item( -// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), -// false, -// false, -// Some(0), -// cx, -// ); -// }); -// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); - -// // b. Add after the active item -// set_labeled_items(&pane, ["A", "B*", "C"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item( -// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), -// false, -// false, -// Some(2), -// cx, -// ); -// }); -// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); - -// // c. Add at the end of the item list (including off the length) -// set_labeled_items(&pane, ["A", "B*", "C"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item( -// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), -// false, -// false, -// Some(5), -// cx, -// ); -// }); -// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - -// // 2. Add without a destination index -// // a. Add with active item at the start of the item list -// set_labeled_items(&pane, ["A*", "B", "C"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item( -// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), -// false, -// false, -// None, -// cx, -// ); -// }); -// set_labeled_items(&pane, ["A", "D*", "B", "C"], cx); - -// // b. Add with active item at the end of the item list -// set_labeled_items(&pane, ["A", "B", "C*"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item( -// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), -// false, -// false, -// None, -// cx, -// ); -// }); -// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); -// } + #[gpui::test] + async fn test_add_item_with_new_item(cx: &mut 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.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + // 1. Add with a destination index + // a. Add before the active item + set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| TestItem::new(cx).with_label("D"))), + false, + false, + Some(0), + cx, + ); + }); + assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); + + // b. Add after the active item + set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| TestItem::new(cx).with_label("D"))), + false, + false, + Some(2), + cx, + ); + }); + assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); + + // c. Add at the end of the item list (including off the length) + set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| TestItem::new(cx).with_label("D"))), + false, + false, + Some(5), + cx, + ); + }); + assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + + // 2. Add without a destination index + // a. Add with active item at the start of the item list + set_labeled_items(&pane, ["A*", "B", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| TestItem::new(cx).with_label("D"))), + false, + false, + None, + cx, + ); + }); + set_labeled_items(&pane, ["A", "D*", "B", "C"], cx); + + // b. Add with active item at the end of the item list + set_labeled_items(&pane, ["A", "B", "C*"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| TestItem::new(cx).with_label("D"))), + false, + false, + None, + cx, + ); + }); + assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + } -// #[gpui::test] -// async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { -// cx.foreground().forbid_parking(); -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// // 1. Add with a destination index -// // 1a. Add before the active item -// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item(d, false, false, Some(0), cx); -// }); -// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); - -// // 1b. Add after the active item -// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item(d, false, false, Some(2), cx); -// }); -// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); - -// // 1c. Add at the end of the item list (including off the length) -// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item(a, false, false, Some(5), cx); -// }); -// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); - -// // 1d. Add same item to active index -// let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item(b, false, false, Some(1), cx); -// }); -// assert_item_labels(&pane, ["A", "B*", "C"], cx); - -// // 1e. Add item to index after same item in last position -// let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item(c, false, false, Some(2), cx); -// }); -// assert_item_labels(&pane, ["A", "B", "C*"], cx); - -// // 2. Add without a destination index -// // 2a. Add with active item at the start of the item list -// let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item(d, false, false, None, cx); -// }); -// assert_item_labels(&pane, ["A", "D*", "B", "C"], cx); - -// // 2b. Add with active item at the end of the item list -// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item(a, false, false, None, cx); -// }); -// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); - -// // 2c. Add active item to active item at end of list -// let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item(c, false, false, None, cx); -// }); -// assert_item_labels(&pane, ["A", "B", "C*"], cx); - -// // 2d. Add active item to active item at start of list -// let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx); -// pane.update(cx, |pane, cx| { -// pane.add_item(a, false, false, None, cx); -// }); -// assert_item_labels(&pane, ["A*", "B", "C"], cx); -// } + #[gpui::test] + async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); -// #[gpui::test] -// async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) { -// cx.foreground().forbid_parking(); -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// // singleton view -// pane.update(cx, |pane, cx| { -// let item = TestItem::new() -// .with_singleton(true) -// .with_label("buffer 1") -// .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); - -// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); -// }); -// assert_item_labels(&pane, ["buffer 1*"], cx); - -// // new singleton view with the same project entry -// pane.update(cx, |pane, cx| { -// let item = TestItem::new() -// .with_singleton(true) -// .with_label("buffer 1") -// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); - -// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); -// }); -// assert_item_labels(&pane, ["buffer 1*"], cx); - -// // new singleton view with different project entry -// pane.update(cx, |pane, cx| { -// let item = TestItem::new() -// .with_singleton(true) -// .with_label("buffer 2") -// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); -// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); -// }); -// assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx); - -// // new multibuffer view with the same project entry -// pane.update(cx, |pane, cx| { -// let item = TestItem::new() -// .with_singleton(false) -// .with_label("multibuffer 1") -// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); - -// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); -// }); -// assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx); - -// // another multibuffer view with the same project entry -// pane.update(cx, |pane, cx| { -// let item = TestItem::new() -// .with_singleton(false) -// .with_label("multibuffer 1b") -// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); - -// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); -// }); -// assert_item_labels( -// &pane, -// ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"], -// cx, -// ); -// } + let project = Project::test(fs, None, cx).await; + let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); -// #[gpui::test] -// async fn test_remove_item_ordering(cx: &mut TestAppContext) { -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// add_labeled_item(&pane, "A", false, cx); -// add_labeled_item(&pane, "B", false, cx); -// add_labeled_item(&pane, "C", false, cx); -// add_labeled_item(&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_labeled_item(&pane, "1", false, cx); -// assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - -// pane.update(cx, |pane, cx| { -// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) -// }) -// .unwrap() -// .await -// .unwrap(); -// assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); - -// pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); -// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - -// pane.update(cx, |pane, cx| { -// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) -// }) -// .unwrap() -// .await -// .unwrap(); -// assert_item_labels(&pane, ["A", "B*", "C"], cx); - -// pane.update(cx, |pane, cx| { -// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) -// }) -// .unwrap() -// .await -// .unwrap(); -// assert_item_labels(&pane, ["A", "C*"], cx); - -// pane.update(cx, |pane, cx| { -// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) -// }) -// .unwrap() -// .await -// .unwrap(); -// assert_item_labels(&pane, ["A*"], cx); -// } + // 1. Add with a destination index + // 1a. Add before the active item + let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(d, false, false, Some(0), cx); + }); + assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); -// #[gpui::test] -// async fn test_close_inactive_items(cx: &mut TestAppContext) { -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - -// pane.update(cx, |pane, cx| { -// pane.close_inactive_items(&CloseInactiveItems, cx) -// }) -// .unwrap() -// .await -// .unwrap(); -// assert_item_labels(&pane, ["C*"], cx); -// } + // 1b. Add after the active item + let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(d, false, false, Some(2), cx); + }); + assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); -// #[gpui::test] -// async fn test_close_clean_items(cx: &mut TestAppContext) { -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// add_labeled_item(&pane, "A", true, cx); -// add_labeled_item(&pane, "B", false, cx); -// add_labeled_item(&pane, "C", true, cx); -// add_labeled_item(&pane, "D", false, cx); -// add_labeled_item(&pane, "E", false, cx); -// assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - -// pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) -// .unwrap() -// .await -// .unwrap(); -// assert_item_labels(&pane, ["A^", "C*^"], cx); -// } + // 1c. Add at the end of the item list (including off the length) + let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(a, false, false, Some(5), cx); + }); + assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); -// #[gpui::test] -// async fn test_close_items_to_the_left(cx: &mut TestAppContext) { -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - -// pane.update(cx, |pane, cx| { -// pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) -// }) -// .unwrap() -// .await -// .unwrap(); -// assert_item_labels(&pane, ["C*", "D", "E"], cx); -// } + // 1d. Add same item to active index + let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(b, false, false, Some(1), cx); + }); + assert_item_labels(&pane, ["A", "B*", "C"], cx); -// #[gpui::test] -// async fn test_close_items_to_the_right(cx: &mut TestAppContext) { -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - -// pane.update(cx, |pane, cx| { -// pane.close_items_to_the_right(&CloseItemsToTheRight, cx) -// }) -// .unwrap() -// .await -// .unwrap(); -// assert_item_labels(&pane, ["A", "B", "C*"], cx); -// } + // 1e. Add item to index after same item in last position + let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(c, false, false, Some(2), cx); + }); + assert_item_labels(&pane, ["A", "B", "C*"], cx); -// #[gpui::test] -// async fn test_close_all_items(cx: &mut TestAppContext) { -// init_test(cx); -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); -// let workspace = window.root(cx); -// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// add_labeled_item(&pane, "A", false, cx); -// add_labeled_item(&pane, "B", false, cx); -// add_labeled_item(&pane, "C", false, cx); -// assert_item_labels(&pane, ["A", "B", "C*"], cx); - -// pane.update(cx, |pane, cx| { -// pane.close_all_items(&CloseAllItems { save_intent: None }, cx) -// }) -// .unwrap() -// .await -// .unwrap(); -// assert_item_labels(&pane, [], cx); - -// add_labeled_item(&pane, "A", true, cx); -// add_labeled_item(&pane, "B", true, cx); -// add_labeled_item(&pane, "C", true, cx); -// assert_item_labels(&pane, ["A^", "B^", "C*^"], cx); - -// let save = pane -// .update(cx, |pane, cx| { -// pane.close_all_items(&CloseAllItems { save_intent: None }, cx) -// }) -// .unwrap(); - -// cx.foreground().run_until_parked(); -// window.simulate_prompt_answer(2, cx); -// save.await.unwrap(); -// assert_item_labels(&pane, [], cx); -// } + // 2. Add without a destination index + // 2a. Add with active item at the start of the item list + let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(d, false, false, None, cx); + }); + assert_item_labels(&pane, ["A", "D*", "B", "C"], cx); -// fn init_test(cx: &mut TestAppContext) { -// cx.update(|cx| { -// cx.set_global(SettingsStore::test(cx)); -// theme::init((), cx); -// crate::init_settings(cx); -// Project::init_settings(cx); -// }); -// } + // 2b. Add with active item at the end of the item list + let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(a, false, false, None, cx); + }); + assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); -// fn add_labeled_item( -// pane: &ViewHandle, -// label: &str, -// is_dirty: bool, -// cx: &mut TestAppContext, -// ) -> Box> { -// pane.update(cx, |pane, cx| { -// let labeled_item = -// Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty))); -// pane.add_item(labeled_item.clone(), false, false, None, cx); -// labeled_item -// }) -// } + // 2c. Add active item to active item at end of list + let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(c, false, false, None, cx); + }); + assert_item_labels(&pane, ["A", "B", "C*"], cx); -// fn set_labeled_items( -// pane: &ViewHandle, -// labels: [&str; COUNT], -// cx: &mut TestAppContext, -// ) -> [Box>; COUNT] { -// pane.update(cx, |pane, cx| { -// pane.items.clear(); -// let mut active_item_index = 0; - -// let mut index = 0; -// let items = labels.map(|mut label| { -// if label.ends_with("*") { -// label = label.trim_end_matches("*"); -// active_item_index = index; -// } - -// let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label))); -// pane.add_item(labeled_item.clone(), false, false, None, cx); -// index += 1; -// labeled_item -// }); - -// pane.activate_item(active_item_index, false, false, cx); - -// items -// }) -// } + // 2d. Add active item to active item at start of list + let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(a, false, false, None, cx); + }); + assert_item_labels(&pane, ["A*", "B", "C"], cx); + } + + #[gpui::test] + async fn test_add_item_with_same_project_entries(cx: &mut 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.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + // singleton view + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| { + TestItem::new(cx) + .with_singleton(true) + .with_label("buffer 1") + .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) + })), + false, + false, + None, + cx, + ); + }); + assert_item_labels(&pane, ["buffer 1*"], cx); + + // new singleton view with the same project entry + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| { + TestItem::new(cx) + .with_singleton(true) + .with_label("buffer 1") + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) + })), + false, + false, + None, + cx, + ); + }); + assert_item_labels(&pane, ["buffer 1*"], cx); + + // new singleton view with different project entry + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| { + TestItem::new(cx) + .with_singleton(true) + .with_label("buffer 2") + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) + })), + false, + false, + None, + cx, + ); + }); + assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx); + + // new multibuffer view with the same project entry + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| { + TestItem::new(cx) + .with_singleton(false) + .with_label("multibuffer 1") + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) + })), + false, + false, + None, + cx, + ); + }); + assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx); + + // another multibuffer view with the same project entry + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.build_view(|cx| { + TestItem::new(cx) + .with_singleton(false) + .with_label("multibuffer 1b") + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) + })), + false, + false, + None, + cx, + ); + }); + assert_item_labels( + &pane, + ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"], + cx, + ); + } -// // Assert the item label, with the active item label suffixed with a '*' -// fn assert_item_labels( -// pane: &ViewHandle, -// expected_states: [&str; COUNT], -// cx: &mut TestAppContext, -// ) { -// pane.read_with(cx, |pane, cx| { -// let actual_states = pane -// .items -// .iter() -// .enumerate() -// .map(|(ix, item)| { -// let mut state = item -// .as_any() -// .downcast_ref::() -// .unwrap() -// .read(cx) -// .label -// .clone(); -// if ix == pane.active_item_index { -// state.push('*'); -// } -// if item.is_dirty(cx) { -// state.push('^'); -// } -// state -// }) -// .collect::>(); - -// assert_eq!( -// actual_states, expected_states, -// "pane items do not match expectation" -// ); -// }) -// } -// } + #[gpui::test] + async fn test_remove_item_ordering(cx: &mut 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.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + add_labeled_item(&pane, "A", false, cx); + add_labeled_item(&pane, "B", false, cx); + add_labeled_item(&pane, "C", false, cx); + add_labeled_item(&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_labeled_item(&pane, "1", false, cx); + assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); + + pane.update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); + + pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); + assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + + pane.update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A", "B*", "C"], cx); + + pane.update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A", "C*"], cx); + + pane.update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A*"], cx); + } + + #[gpui::test] + async fn test_close_inactive_items(cx: &mut 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.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + + pane.update(cx, |pane, cx| { + pane.close_inactive_items(&CloseInactiveItems, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["C*"], cx); + } + + #[gpui::test] + async fn test_close_clean_items(cx: &mut 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.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + add_labeled_item(&pane, "A", true, cx); + add_labeled_item(&pane, "B", false, cx); + add_labeled_item(&pane, "C", true, cx); + add_labeled_item(&pane, "D", false, cx); + add_labeled_item(&pane, "E", false, cx); + assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); + + pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A^", "C*^"], cx); + } + + #[gpui::test] + async fn test_close_items_to_the_left(cx: &mut 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.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["C*", "D", "E"], cx); + } + + #[gpui::test] + async fn test_close_items_to_the_right(cx: &mut 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.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right(&CloseItemsToTheRight, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + } + + #[gpui::test] + async fn test_close_all_items(cx: &mut 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.clone(), cx)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + add_labeled_item(&pane, "A", false, cx); + add_labeled_item(&pane, "B", false, cx); + add_labeled_item(&pane, "C", false, cx); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + pane.update(cx, |pane, cx| { + pane.close_all_items(&CloseAllItems { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, [], cx); + + add_labeled_item(&pane, "A", true, cx); + add_labeled_item(&pane, "B", true, cx); + add_labeled_item(&pane, "C", true, cx); + assert_item_labels(&pane, ["A^", "B^", "C*^"], cx); + + let save = pane + .update(cx, |pane, cx| { + pane.close_all_items(&CloseAllItems { save_intent: None }, cx) + }) + .unwrap(); + + cx.executor().run_until_parked(); + cx.simulate_prompt_answer(2); + save.await.unwrap(); + assert_item_labels(&pane, [], cx); + } + + fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + theme::init(LoadThemes::JustBase, cx); + crate::init_settings(cx); + Project::init_settings(cx); + }); + } + + fn add_labeled_item( + pane: &View, + label: &str, + is_dirty: bool, + cx: &mut VisualTestContext, + ) -> Box> { + pane.update(cx, |pane, cx| { + let labeled_item = Box::new( + cx.build_view(|cx| TestItem::new(cx).with_label(label).with_dirty(is_dirty)), + ); + pane.add_item(labeled_item.clone(), false, false, None, cx); + labeled_item + }) + } + + fn set_labeled_items( + pane: &View, + labels: [&str; COUNT], + cx: &mut VisualTestContext, + ) -> [Box>; COUNT] { + pane.update(cx, |pane, cx| { + pane.items.clear(); + let mut active_item_index = 0; + + let mut index = 0; + let items = labels.map(|mut label| { + if label.ends_with("*") { + label = label.trim_end_matches("*"); + active_item_index = index; + } + + let labeled_item = + Box::new(cx.build_view(|cx| TestItem::new(cx).with_label(label))); + pane.add_item(labeled_item.clone(), false, false, None, cx); + index += 1; + labeled_item + }); + + pane.activate_item(active_item_index, false, false, cx); + + items + }) + } + + // Assert the item label, with the active item label suffixed with a '*' + fn assert_item_labels( + pane: &View, + expected_states: [&str; COUNT], + cx: &mut VisualTestContext, + ) { + pane.update(cx, |pane, cx| { + let actual_states = pane + .items + .iter() + .enumerate() + .map(|(ix, item)| { + let mut state = item + .to_any() + .downcast::() + .unwrap() + .read(cx) + .label + .clone(); + if ix == pane.active_item_index { + state.push('*'); + } + if item.is_dirty(cx) { + state.push('^'); + } + state + }) + .collect::>(); + + assert_eq!( + actual_states, expected_states, + "pane items do not match expectation" + ); + }) + } +} impl Render for DraggedTab { type Element = ::Rendered;