pane.rs

   1use crate::{
   2    item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle},
   3    toolbar::Toolbar,
   4    workspace_settings::{AutosaveSetting, WorkspaceSettings},
   5    NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
   6};
   7use anyhow::Result;
   8use collections::{HashMap, HashSet, VecDeque};
   9use gpui::{
  10    actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AppContext,
  11    AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable,
  12    FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render,
  13    ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
  14};
  15use parking_lot::Mutex;
  16use project::{Project, ProjectEntryId, ProjectPath};
  17use serde::Deserialize;
  18use settings::Settings;
  19use std::{
  20    any::Any,
  21    cmp, fmt, mem,
  22    path::{Path, PathBuf},
  23    sync::{
  24        atomic::{AtomicUsize, Ordering},
  25        Arc,
  26    },
  27};
  28use theme::ThemeSettings;
  29
  30use ui::{
  31    h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize,
  32    Indicator, Label, Tab, TabBar, TabPosition, Tooltip,
  33};
  34use ui::{v_stack, ContextMenu};
  35use util::{maybe, truncate_and_remove_front, ResultExt};
  36
  37#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
  38#[serde(rename_all = "camelCase")]
  39pub enum SaveIntent {
  40    /// write all files (even if unchanged)
  41    /// prompt before overwriting on-disk changes
  42    Save,
  43    /// write any files that have local changes
  44    /// prompt before overwriting on-disk changes
  45    SaveAll,
  46    /// always prompt for a new path
  47    SaveAs,
  48    /// prompt "you have unsaved changes" before writing
  49    Close,
  50    /// write all dirty files, don't prompt on conflict
  51    Overwrite,
  52    /// skip all save-related behavior
  53    Skip,
  54}
  55
  56#[derive(Clone, Deserialize, PartialEq, Debug)]
  57pub struct ActivateItem(pub usize);
  58
  59// #[derive(Clone, PartialEq)]
  60// pub struct CloseItemById {
  61//     pub item_id: usize,
  62//     pub pane: WeakView<Pane>,
  63// }
  64
  65// #[derive(Clone, PartialEq)]
  66// pub struct CloseItemsToTheLeftById {
  67//     pub item_id: usize,
  68//     pub pane: WeakView<Pane>,
  69// }
  70
  71// #[derive(Clone, PartialEq)]
  72// pub struct CloseItemsToTheRightById {
  73//     pub item_id: usize,
  74//     pub pane: WeakView<Pane>,
  75// }
  76
  77#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
  78#[serde(rename_all = "camelCase")]
  79pub struct CloseActiveItem {
  80    pub save_intent: Option<SaveIntent>,
  81}
  82
  83#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
  84#[serde(rename_all = "camelCase")]
  85pub struct CloseAllItems {
  86    pub save_intent: Option<SaveIntent>,
  87}
  88
  89#[derive(Clone, PartialEq, Debug, Deserialize)]
  90#[serde(rename_all = "camelCase")]
  91pub struct RevealInProjectPanel {
  92    pub entry_id: u64,
  93}
  94
  95impl_actions!(
  96    pane,
  97    [
  98        CloseAllItems,
  99        CloseActiveItem,
 100        ActivateItem,
 101        RevealInProjectPanel
 102    ]
 103);
 104
 105actions!(
 106    pane,
 107    [
 108        ActivatePrevItem,
 109        ActivateNextItem,
 110        ActivateLastItem,
 111        CloseInactiveItems,
 112        CloseCleanItems,
 113        CloseItemsToTheLeft,
 114        CloseItemsToTheRight,
 115        GoBack,
 116        GoForward,
 117        ReopenClosedItem,
 118        SplitLeft,
 119        SplitUp,
 120        SplitRight,
 121        SplitDown,
 122    ]
 123);
 124
 125const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 126
 127pub enum Event {
 128    AddItem { item: Box<dyn ItemHandle> },
 129    ActivateItem { local: bool },
 130    Remove,
 131    RemoveItem { item_id: EntityId },
 132    Split(SplitDirection),
 133    ChangeItemTitle,
 134    Focus,
 135    ZoomIn,
 136    ZoomOut,
 137}
 138
 139impl fmt::Debug for Event {
 140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 141        match self {
 142            Event::AddItem { item } => f
 143                .debug_struct("AddItem")
 144                .field("item", &item.item_id())
 145                .finish(),
 146            Event::ActivateItem { local } => f
 147                .debug_struct("ActivateItem")
 148                .field("local", local)
 149                .finish(),
 150            Event::Remove => f.write_str("Remove"),
 151            Event::RemoveItem { item_id } => f
 152                .debug_struct("RemoveItem")
 153                .field("item_id", item_id)
 154                .finish(),
 155            Event::Split(direction) => f
 156                .debug_struct("Split")
 157                .field("direction", direction)
 158                .finish(),
 159            Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),
 160            Event::Focus => f.write_str("Focus"),
 161            Event::ZoomIn => f.write_str("ZoomIn"),
 162            Event::ZoomOut => f.write_str("ZoomOut"),
 163        }
 164    }
 165}
 166
 167pub struct Pane {
 168    focus_handle: FocusHandle,
 169    items: Vec<Box<dyn ItemHandle>>,
 170    activation_history: Vec<EntityId>,
 171    zoomed: bool,
 172    was_focused: bool,
 173    active_item_index: usize,
 174    last_focused_view_by_item: HashMap<EntityId, FocusHandle>,
 175    nav_history: NavHistory,
 176    toolbar: View<Toolbar>,
 177    new_item_menu: Option<View<ContextMenu>>,
 178    split_item_menu: Option<View<ContextMenu>>,
 179    //     tab_context_menu: ViewHandle<ContextMenu>,
 180    workspace: WeakView<Workspace>,
 181    project: Model<Project>,
 182    //     can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
 183    can_split: bool,
 184    //     render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
 185    _subscriptions: Vec<Subscription>,
 186    tab_bar_scroll_handle: ScrollHandle,
 187}
 188
 189pub struct ItemNavHistory {
 190    history: NavHistory,
 191    item: Arc<dyn WeakItemHandle>,
 192}
 193
 194#[derive(Clone)]
 195pub struct NavHistory(Arc<Mutex<NavHistoryState>>);
 196
 197struct NavHistoryState {
 198    mode: NavigationMode,
 199    backward_stack: VecDeque<NavigationEntry>,
 200    forward_stack: VecDeque<NavigationEntry>,
 201    closed_stack: VecDeque<NavigationEntry>,
 202    paths_by_item: HashMap<EntityId, (ProjectPath, Option<PathBuf>)>,
 203    pane: WeakView<Pane>,
 204    next_timestamp: Arc<AtomicUsize>,
 205}
 206
 207#[derive(Copy, Clone)]
 208pub enum NavigationMode {
 209    Normal,
 210    GoingBack,
 211    GoingForward,
 212    ClosingItem,
 213    ReopeningClosedItem,
 214    Disabled,
 215}
 216
 217impl Default for NavigationMode {
 218    fn default() -> Self {
 219        Self::Normal
 220    }
 221}
 222
 223pub struct NavigationEntry {
 224    pub item: Arc<dyn WeakItemHandle>,
 225    pub data: Option<Box<dyn Any + Send>>,
 226    pub timestamp: usize,
 227}
 228
 229#[derive(Clone)]
 230struct DraggedTab {
 231    pub pane: View<Pane>,
 232    pub ix: usize,
 233    pub item_id: EntityId,
 234    pub detail: usize,
 235    pub is_active: bool,
 236}
 237
 238// pub struct DraggedItem {
 239//     pub handle: Box<dyn ItemHandle>,
 240//     pub pane: WeakView<Pane>,
 241// }
 242
 243// pub enum ReorderBehavior {
 244//     None,
 245//     MoveAfterActive,
 246//     MoveToIndex(usize),
 247// }
 248
 249// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 250// enum TabBarContextMenuKind {
 251//     New,
 252//     Split,
 253// }
 254
 255// struct TabBarContextMenu {
 256//     kind: TabBarContextMenuKind,
 257//     handle: ViewHandle<ContextMenu>,
 258// }
 259
 260// impl TabBarContextMenu {
 261//     fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<ViewHandle<ContextMenu>> {
 262//         if self.kind == kind {
 263//             return Some(self.handle.clone());
 264//         }
 265//         None
 266//     }
 267// }
 268
 269// #[allow(clippy::too_many_arguments)]
 270// fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
 271//     svg_path: &'static str,
 272//     style: theme2::Interactive<theme2::IconButton>,
 273//     nav_button_height: f32,
 274//     tooltip_style: TooltipStyle,
 275//     enabled: bool,
 276//     on_click: F,
 277//     tooltip_action: A,
 278//     action_name: &str,
 279//     cx: &mut ViewContext<Pane>,
 280// ) -> AnyElement<Pane> {
 281//     MouseEventHandler::new::<A, _>(0, cx, |state, _| {
 282//         let style = if enabled {
 283//             style.style_for(state)
 284//         } else {
 285//             style.disabled_style()
 286//         };
 287//         Svg::new(svg_path)
 288//             .with_color(style.color)
 289//             .constrained()
 290//             .with_width(style.icon_width)
 291//             .aligned()
 292//             .contained()
 293//             .with_style(style.container)
 294//             .constrained()
 295//             .with_width(style.button_width)
 296//             .with_height(nav_button_height)
 297//             .aligned()
 298//             .top()
 299//     })
 300//     .with_cursor_style(if enabled {
 301//         CursorStyle::PointingHand
 302//     } else {
 303//         CursorStyle::default()
 304//     })
 305//     .on_click(MouseButton::Left, move |_, toolbar, cx| {
 306//         on_click(toolbar, cx)
 307//     })
 308//     .with_tooltip::<A>(
 309//         0,
 310//         action_name.to_string(),
 311//         Some(Box::new(tooltip_action)),
 312//         tooltip_style,
 313//         cx,
 314//     )
 315//     .contained()
 316//     .into_any_named("nav button")
 317// }
 318
 319impl EventEmitter<Event> for Pane {}
 320
 321impl Pane {
 322    pub fn new(
 323        workspace: WeakView<Workspace>,
 324        project: Model<Project>,
 325        next_timestamp: Arc<AtomicUsize>,
 326        cx: &mut ViewContext<Self>,
 327    ) -> Self {
 328        // todo!("context menu")
 329        // let pane_view_id = cx.view_id();
 330        // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
 331        // context_menu.update(cx, |menu, _| {
 332        //     menu.set_position_mode(OverlayPositionMode::Local)
 333        // });
 334        //
 335        let focus_handle = cx.focus_handle();
 336
 337        let subscriptions = vec![
 338            cx.on_focus_in(&focus_handle, move |this, cx| this.focus_in(cx)),
 339            cx.on_focus_out(&focus_handle, move |this, cx| this.focus_out(cx)),
 340        ];
 341
 342        let handle = cx.view().downgrade();
 343        Self {
 344            focus_handle,
 345            items: Vec::new(),
 346            activation_history: Vec::new(),
 347            was_focused: false,
 348            zoomed: false,
 349            active_item_index: 0,
 350            last_focused_view_by_item: Default::default(),
 351            nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
 352                mode: NavigationMode::Normal,
 353                backward_stack: Default::default(),
 354                forward_stack: Default::default(),
 355                closed_stack: Default::default(),
 356                paths_by_item: Default::default(),
 357                pane: handle.clone(),
 358                next_timestamp,
 359            }))),
 360            toolbar: cx.build_view(|_| Toolbar::new()),
 361            new_item_menu: None,
 362            split_item_menu: None,
 363            tab_bar_scroll_handle: ScrollHandle::new(),
 364            // tab_bar_context_menu: TabBarContextMenu {
 365            //     kind: TabBarContextMenuKind::New,
 366            //     handle: context_menu,
 367            // },
 368            // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
 369            workspace,
 370            project,
 371            // can_drop: Rc::new(|_, _| true),
 372            can_split: true,
 373            // render_tab_bar_buttons: Rc::new(move |pane, cx| {
 374            //     Flex::row()
 375            //         // New menu
 376            //         .with_child(Self::render_tab_bar_button(
 377            //             0,
 378            //             "icons/plus.svg",
 379            //             false,
 380            //             Some(("New...".into(), None)),
 381            //             cx,
 382            //             |pane, cx| pane.deploy_new_menu(cx),
 383            //             |pane, cx| {
 384            //                 pane.tab_bar_context_menu
 385            //                     .handle
 386            //                     .update(cx, |menu, _| menu.delay_cancel())
 387            //             },
 388            //             pane.tab_bar_context_menu
 389            //                 .handle_if_kind(TabBarContextMenuKind::New),
 390            //         ))
 391            //         .with_child(Self::render_tab_bar_button(
 392            //             1,
 393            //             "icons/split.svg",
 394            //             false,
 395            //             Some(("Split Pane".into(), None)),
 396            //             cx,
 397            //             |pane, cx| pane.deploy_split_menu(cx),
 398            //             |pane, cx| {
 399            //                 pane.tab_bar_context_menu
 400            //                     .handle
 401            //                     .update(cx, |menu, _| menu.delay_cancel())
 402            //             },
 403            //             pane.tab_bar_context_menu
 404            //                 .handle_if_kind(TabBarContextMenuKind::Split),
 405            //         ))
 406            //         .with_child({
 407            //             let icon_path;
 408            //             let tooltip_label;
 409            //             if pane.is_zoomed() {
 410            //                 icon_path = "icons/minimize.svg";
 411            //                 tooltip_label = "Zoom In";
 412            //             } else {
 413            //                 icon_path = "icons/maximize.svg";
 414            //                 tooltip_label = "Zoom In";
 415            //             }
 416
 417            //             Pane::render_tab_bar_button(
 418            //                 2,
 419            //                 icon_path,
 420            //                 pane.is_zoomed(),
 421            //                 Some((tooltip_label, Some(Box::new(ToggleZoom)))),
 422            //                 cx,
 423            //                 move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
 424            //                 move |_, _| {},
 425            //                 None,
 426            //             )
 427            //         })
 428            //         .into_any()
 429            // }),
 430            _subscriptions: subscriptions,
 431        }
 432    }
 433
 434    pub fn has_focus(&self, cx: &WindowContext) -> bool {
 435        // todo!(); // inline this manually
 436        self.focus_handle.contains_focused(cx)
 437    }
 438
 439    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
 440        if !self.was_focused {
 441            self.was_focused = true;
 442            cx.emit(Event::Focus);
 443            cx.notify();
 444        }
 445
 446        self.toolbar.update(cx, |toolbar, cx| {
 447            toolbar.focus_changed(true, cx);
 448        });
 449
 450        if let Some(active_item) = self.active_item() {
 451            if self.focus_handle.is_focused(cx) {
 452                // Pane was focused directly. We need to either focus a view inside the active item,
 453                // or focus the active item itself
 454                if let Some(weak_last_focused_view) =
 455                    self.last_focused_view_by_item.get(&active_item.item_id())
 456                {
 457                    weak_last_focused_view.focus(cx);
 458                    return;
 459                }
 460
 461                active_item.focus_handle(cx).focus(cx);
 462            } else if let Some(focused) = cx.focused() {
 463                if !self.context_menu_focused(cx) {
 464                    self.last_focused_view_by_item
 465                        .insert(active_item.item_id(), focused);
 466                }
 467            }
 468        }
 469    }
 470
 471    fn context_menu_focused(&self, cx: &mut ViewContext<Self>) -> bool {
 472        self.new_item_menu
 473            .as_ref()
 474            .or(self.split_item_menu.as_ref())
 475            .map_or(false, |menu| menu.focus_handle(cx).is_focused(cx))
 476    }
 477
 478    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
 479        self.was_focused = false;
 480        self.toolbar.update(cx, |toolbar, cx| {
 481            toolbar.focus_changed(false, cx);
 482        });
 483        cx.notify();
 484    }
 485
 486    pub fn active_item_index(&self) -> usize {
 487        self.active_item_index
 488    }
 489
 490    //     pub fn on_can_drop<F>(&mut self, can_drop: F)
 491    //     where
 492    //         F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
 493    //     {
 494    //         self.can_drop = Rc::new(can_drop);
 495    //     }
 496
 497    pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
 498        self.can_split = can_split;
 499        cx.notify();
 500    }
 501
 502    pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
 503        self.toolbar.update(cx, |toolbar, cx| {
 504            toolbar.set_can_navigate(can_navigate, cx);
 505        });
 506        cx.notify();
 507    }
 508
 509    //     pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
 510    //     where
 511    //         F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
 512    //     {
 513    //         self.render_tab_bar_buttons = Rc::new(render);
 514    //         cx.notify();
 515    //     }
 516
 517    pub fn nav_history_for_item<T: Item>(&self, item: &View<T>) -> ItemNavHistory {
 518        ItemNavHistory {
 519            history: self.nav_history.clone(),
 520            item: Arc::new(item.downgrade()),
 521        }
 522    }
 523
 524    pub fn nav_history(&self) -> &NavHistory {
 525        &self.nav_history
 526    }
 527
 528    pub fn nav_history_mut(&mut self) -> &mut NavHistory {
 529        &mut self.nav_history
 530    }
 531
 532    pub fn disable_history(&mut self) {
 533        self.nav_history.disable();
 534    }
 535
 536    pub fn enable_history(&mut self) {
 537        self.nav_history.enable();
 538    }
 539
 540    pub fn can_navigate_backward(&self) -> bool {
 541        !self.nav_history.0.lock().backward_stack.is_empty()
 542    }
 543
 544    pub fn can_navigate_forward(&self) -> bool {
 545        !self.nav_history.0.lock().forward_stack.is_empty()
 546    }
 547
 548    fn navigate_backward(&mut self, cx: &mut ViewContext<Self>) {
 549        if let Some(workspace) = self.workspace.upgrade() {
 550            let pane = cx.view().downgrade();
 551            cx.window_context().defer(move |cx| {
 552                workspace.update(cx, |workspace, cx| {
 553                    workspace.go_back(pane, cx).detach_and_log_err(cx)
 554                })
 555            })
 556        }
 557    }
 558
 559    fn navigate_forward(&mut self, cx: &mut ViewContext<Self>) {
 560        if let Some(workspace) = self.workspace.upgrade() {
 561            let pane = cx.view().downgrade();
 562            cx.window_context().defer(move |cx| {
 563                workspace.update(cx, |workspace, cx| {
 564                    workspace.go_forward(pane, cx).detach_and_log_err(cx)
 565                })
 566            })
 567        }
 568    }
 569
 570    fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
 571        self.toolbar.update(cx, |_, cx| cx.notify());
 572    }
 573
 574    pub(crate) fn open_item(
 575        &mut self,
 576        project_entry_id: Option<ProjectEntryId>,
 577        focus_item: bool,
 578        cx: &mut ViewContext<Self>,
 579        build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 580    ) -> Box<dyn ItemHandle> {
 581        let mut existing_item = None;
 582        if let Some(project_entry_id) = project_entry_id {
 583            for (index, item) in self.items.iter().enumerate() {
 584                if item.is_singleton(cx)
 585                    && item.project_entry_ids(cx).as_slice() == [project_entry_id]
 586                {
 587                    let item = item.boxed_clone();
 588                    existing_item = Some((index, item));
 589                    break;
 590                }
 591            }
 592        }
 593
 594        if let Some((index, existing_item)) = existing_item {
 595            self.activate_item(index, focus_item, focus_item, cx);
 596            existing_item
 597        } else {
 598            let new_item = build_item(cx);
 599            self.add_item(new_item.clone(), true, focus_item, None, cx);
 600            new_item
 601        }
 602    }
 603
 604    pub fn add_item(
 605        &mut self,
 606        item: Box<dyn ItemHandle>,
 607        activate_pane: bool,
 608        focus_item: bool,
 609        destination_index: Option<usize>,
 610        cx: &mut ViewContext<Self>,
 611    ) {
 612        if item.is_singleton(cx) {
 613            if let Some(&entry_id) = item.project_entry_ids(cx).get(0) {
 614                let project = self.project.read(cx);
 615                if let Some(project_path) = project.path_for_entry(entry_id, cx) {
 616                    let abs_path = project.absolute_path(&project_path, cx);
 617                    self.nav_history
 618                        .0
 619                        .lock()
 620                        .paths_by_item
 621                        .insert(item.item_id(), (project_path, abs_path));
 622                }
 623            }
 624        }
 625        // If no destination index is specified, add or move the item after the active item.
 626        let mut insertion_index = {
 627            cmp::min(
 628                if let Some(destination_index) = destination_index {
 629                    destination_index
 630                } else {
 631                    self.active_item_index + 1
 632                },
 633                self.items.len(),
 634            )
 635        };
 636
 637        // Does the item already exist?
 638        let project_entry_id = if item.is_singleton(cx) {
 639            item.project_entry_ids(cx).get(0).copied()
 640        } else {
 641            None
 642        };
 643
 644        let existing_item_index = self.items.iter().position(|existing_item| {
 645            if existing_item.item_id() == item.item_id() {
 646                true
 647            } else if existing_item.is_singleton(cx) {
 648                existing_item
 649                    .project_entry_ids(cx)
 650                    .get(0)
 651                    .map_or(false, |existing_entry_id| {
 652                        Some(existing_entry_id) == project_entry_id.as_ref()
 653                    })
 654            } else {
 655                false
 656            }
 657        });
 658
 659        if let Some(existing_item_index) = existing_item_index {
 660            // If the item already exists, move it to the desired destination and activate it
 661
 662            if existing_item_index != insertion_index {
 663                let existing_item_is_active = existing_item_index == self.active_item_index;
 664
 665                // If the caller didn't specify a destination and the added item is already
 666                // the active one, don't move it
 667                if existing_item_is_active && destination_index.is_none() {
 668                    insertion_index = existing_item_index;
 669                } else {
 670                    self.items.remove(existing_item_index);
 671                    if existing_item_index < self.active_item_index {
 672                        self.active_item_index -= 1;
 673                    }
 674                    insertion_index = insertion_index.min(self.items.len());
 675
 676                    self.items.insert(insertion_index, item.clone());
 677
 678                    if existing_item_is_active {
 679                        self.active_item_index = insertion_index;
 680                    } else if insertion_index <= self.active_item_index {
 681                        self.active_item_index += 1;
 682                    }
 683                }
 684
 685                cx.notify();
 686            }
 687
 688            self.activate_item(insertion_index, activate_pane, focus_item, cx);
 689        } else {
 690            self.items.insert(insertion_index, item.clone());
 691            if insertion_index <= self.active_item_index {
 692                self.active_item_index += 1;
 693            }
 694
 695            self.activate_item(insertion_index, activate_pane, focus_item, cx);
 696            cx.notify();
 697        }
 698
 699        cx.emit(Event::AddItem { item });
 700    }
 701
 702    pub fn items_len(&self) -> usize {
 703        self.items.len()
 704    }
 705
 706    pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
 707        self.items.iter()
 708    }
 709
 710    pub fn items_of_type<T: Render>(&self) -> impl '_ + Iterator<Item = View<T>> {
 711        self.items
 712            .iter()
 713            .filter_map(|item| item.to_any().downcast().ok())
 714    }
 715
 716    pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
 717        self.items.get(self.active_item_index).cloned()
 718    }
 719
 720    pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
 721        self.items
 722            .get(self.active_item_index)?
 723            .pixel_position_of_cursor(cx)
 724    }
 725
 726    pub fn item_for_entry(
 727        &self,
 728        entry_id: ProjectEntryId,
 729        cx: &AppContext,
 730    ) -> Option<Box<dyn ItemHandle>> {
 731        self.items.iter().find_map(|item| {
 732            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
 733                Some(item.boxed_clone())
 734            } else {
 735                None
 736            }
 737        })
 738    }
 739
 740    pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
 741        self.items
 742            .iter()
 743            .position(|i| i.item_id() == item.item_id())
 744    }
 745
 746    pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
 747        if self.zoomed {
 748            cx.emit(Event::ZoomOut);
 749        } else if !self.items.is_empty() {
 750            if !self.focus_handle.contains_focused(cx) {
 751                cx.focus_self();
 752            }
 753            cx.emit(Event::ZoomIn);
 754        }
 755    }
 756
 757    pub fn activate_item(
 758        &mut self,
 759        index: usize,
 760        activate_pane: bool,
 761        focus_item: bool,
 762        cx: &mut ViewContext<Self>,
 763    ) {
 764        use NavigationMode::{GoingBack, GoingForward};
 765
 766        if index < self.items.len() {
 767            let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
 768            if prev_active_item_ix != self.active_item_index
 769                || matches!(self.nav_history.mode(), GoingBack | GoingForward)
 770            {
 771                if let Some(prev_item) = self.items.get(prev_active_item_ix) {
 772                    prev_item.deactivated(cx);
 773                }
 774
 775                cx.emit(Event::ActivateItem {
 776                    local: activate_pane,
 777                });
 778            }
 779
 780            if let Some(newly_active_item) = self.items.get(index) {
 781                self.activation_history
 782                    .retain(|&previously_active_item_id| {
 783                        previously_active_item_id != newly_active_item.item_id()
 784                    });
 785                self.activation_history.push(newly_active_item.item_id());
 786            }
 787
 788            self.update_toolbar(cx);
 789
 790            if focus_item {
 791                self.focus_active_item(cx);
 792            }
 793
 794            self.tab_bar_scroll_handle.scroll_to_item(index);
 795            cx.notify();
 796        }
 797    }
 798
 799    pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
 800        let mut index = self.active_item_index;
 801        if index > 0 {
 802            index -= 1;
 803        } else if !self.items.is_empty() {
 804            index = self.items.len() - 1;
 805        }
 806        self.activate_item(index, activate_pane, activate_pane, cx);
 807    }
 808
 809    pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
 810        let mut index = self.active_item_index;
 811        if index + 1 < self.items.len() {
 812            index += 1;
 813        } else {
 814            index = 0;
 815        }
 816        self.activate_item(index, activate_pane, activate_pane, cx);
 817    }
 818
 819    pub fn close_active_item(
 820        &mut self,
 821        action: &CloseActiveItem,
 822        cx: &mut ViewContext<Self>,
 823    ) -> Option<Task<Result<()>>> {
 824        if self.items.is_empty() {
 825            return None;
 826        }
 827        let active_item_id = self.items[self.active_item_index].item_id();
 828        Some(self.close_item_by_id(
 829            active_item_id,
 830            action.save_intent.unwrap_or(SaveIntent::Close),
 831            cx,
 832        ))
 833    }
 834
 835    pub fn close_item_by_id(
 836        &mut self,
 837        item_id_to_close: EntityId,
 838        save_intent: SaveIntent,
 839        cx: &mut ViewContext<Self>,
 840    ) -> Task<Result<()>> {
 841        self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
 842    }
 843
 844    pub fn close_inactive_items(
 845        &mut self,
 846        _: &CloseInactiveItems,
 847        cx: &mut ViewContext<Self>,
 848    ) -> Option<Task<Result<()>>> {
 849        if self.items.is_empty() {
 850            return None;
 851        }
 852
 853        let active_item_id = self.items[self.active_item_index].item_id();
 854        Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
 855            item_id != active_item_id
 856        }))
 857    }
 858
 859    pub fn close_clean_items(
 860        &mut self,
 861        _: &CloseCleanItems,
 862        cx: &mut ViewContext<Self>,
 863    ) -> Option<Task<Result<()>>> {
 864        let item_ids: Vec<_> = self
 865            .items()
 866            .filter(|item| !item.is_dirty(cx))
 867            .map(|item| item.item_id())
 868            .collect();
 869        Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
 870            item_ids.contains(&item_id)
 871        }))
 872    }
 873
 874    pub fn close_items_to_the_left(
 875        &mut self,
 876        _: &CloseItemsToTheLeft,
 877        cx: &mut ViewContext<Self>,
 878    ) -> Option<Task<Result<()>>> {
 879        if self.items.is_empty() {
 880            return None;
 881        }
 882        let active_item_id = self.items[self.active_item_index].item_id();
 883        Some(self.close_items_to_the_left_by_id(active_item_id, cx))
 884    }
 885
 886    pub fn close_items_to_the_left_by_id(
 887        &mut self,
 888        item_id: EntityId,
 889        cx: &mut ViewContext<Self>,
 890    ) -> Task<Result<()>> {
 891        let item_ids: Vec<_> = self
 892            .items()
 893            .take_while(|item| item.item_id() != item_id)
 894            .map(|item| item.item_id())
 895            .collect();
 896        self.close_items(cx, SaveIntent::Close, move |item_id| {
 897            item_ids.contains(&item_id)
 898        })
 899    }
 900
 901    pub fn close_items_to_the_right(
 902        &mut self,
 903        _: &CloseItemsToTheRight,
 904        cx: &mut ViewContext<Self>,
 905    ) -> Option<Task<Result<()>>> {
 906        if self.items.is_empty() {
 907            return None;
 908        }
 909        let active_item_id = self.items[self.active_item_index].item_id();
 910        Some(self.close_items_to_the_right_by_id(active_item_id, cx))
 911    }
 912
 913    pub fn close_items_to_the_right_by_id(
 914        &mut self,
 915        item_id: EntityId,
 916        cx: &mut ViewContext<Self>,
 917    ) -> Task<Result<()>> {
 918        let item_ids: Vec<_> = self
 919            .items()
 920            .rev()
 921            .take_while(|item| item.item_id() != item_id)
 922            .map(|item| item.item_id())
 923            .collect();
 924        self.close_items(cx, SaveIntent::Close, move |item_id| {
 925            item_ids.contains(&item_id)
 926        })
 927    }
 928
 929    pub fn close_all_items(
 930        &mut self,
 931        action: &CloseAllItems,
 932        cx: &mut ViewContext<Self>,
 933    ) -> Option<Task<Result<()>>> {
 934        if self.items.is_empty() {
 935            return None;
 936        }
 937
 938        Some(
 939            self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
 940                true
 941            }),
 942        )
 943    }
 944
 945    pub(super) fn file_names_for_prompt(
 946        items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
 947        all_dirty_items: usize,
 948        cx: &AppContext,
 949    ) -> String {
 950        /// Quantity of item paths displayed in prompt prior to cutoff..
 951        const FILE_NAMES_CUTOFF_POINT: usize = 10;
 952        let mut file_names: Vec<_> = items
 953            .filter_map(|item| {
 954                item.project_path(cx).and_then(|project_path| {
 955                    project_path
 956                        .path
 957                        .file_name()
 958                        .and_then(|name| name.to_str().map(ToOwned::to_owned))
 959                })
 960            })
 961            .take(FILE_NAMES_CUTOFF_POINT)
 962            .collect();
 963        let should_display_followup_text =
 964            all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
 965        if should_display_followup_text {
 966            let not_shown_files = all_dirty_items - file_names.len();
 967            if not_shown_files == 1 {
 968                file_names.push(".. 1 file not shown".into());
 969            } else {
 970                file_names.push(format!(".. {} files not shown", not_shown_files).into());
 971            }
 972        }
 973        let file_names = file_names.join("\n");
 974        format!(
 975            "Do you want to save changes to the following {} files?\n{file_names}",
 976            all_dirty_items
 977        )
 978    }
 979
 980    pub fn close_items(
 981        &mut self,
 982        cx: &mut ViewContext<Pane>,
 983        mut save_intent: SaveIntent,
 984        should_close: impl 'static + Fn(EntityId) -> bool,
 985    ) -> Task<Result<()>> {
 986        // Find the items to close.
 987        let mut items_to_close = Vec::new();
 988        let mut dirty_items = Vec::new();
 989        for item in &self.items {
 990            if should_close(item.item_id()) {
 991                items_to_close.push(item.boxed_clone());
 992                if item.is_dirty(cx) {
 993                    dirty_items.push(item.boxed_clone());
 994                }
 995            }
 996        }
 997
 998        // If a buffer is open both in a singleton editor and in a multibuffer, make sure
 999        // to focus the singleton buffer when prompting to save that buffer, as opposed
1000        // to focusing the multibuffer, because this gives the user a more clear idea
1001        // of what content they would be saving.
1002        items_to_close.sort_by_key(|item| !item.is_singleton(cx));
1003
1004        let workspace = self.workspace.clone();
1005        cx.spawn(|pane, mut cx| async move {
1006            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1007                let answer = pane.update(&mut cx, |_, cx| {
1008                    let prompt =
1009                        Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
1010                    cx.prompt(
1011                        PromptLevel::Warning,
1012                        &prompt,
1013                        &["Save all", "Discard all", "Cancel"],
1014                    )
1015                })?;
1016                match answer.await {
1017                    Ok(0) => save_intent = SaveIntent::SaveAll,
1018                    Ok(1) => save_intent = SaveIntent::Skip,
1019                    _ => {}
1020                }
1021            }
1022            let mut saved_project_items_ids = HashSet::default();
1023            for item in items_to_close.clone() {
1024                // Find the item's current index and its set of project item models. Avoid
1025                // storing these in advance, in case they have changed since this task
1026                // was started.
1027                let (item_ix, mut project_item_ids) = pane.update(&mut cx, |pane, cx| {
1028                    (pane.index_for_item(&*item), item.project_item_model_ids(cx))
1029                })?;
1030                let item_ix = if let Some(ix) = item_ix {
1031                    ix
1032                } else {
1033                    continue;
1034                };
1035
1036                // Check if this view has any project items that are not open anywhere else
1037                // in the workspace, AND that the user has not already been prompted to save.
1038                // If there are any such project entries, prompt the user to save this item.
1039                let project = workspace.update(&mut cx, |workspace, cx| {
1040                    for item in workspace.items(cx) {
1041                        if !items_to_close
1042                            .iter()
1043                            .any(|item_to_close| item_to_close.item_id() == item.item_id())
1044                        {
1045                            let other_project_item_ids = item.project_item_model_ids(cx);
1046                            project_item_ids.retain(|id| !other_project_item_ids.contains(id));
1047                        }
1048                    }
1049                    workspace.project().clone()
1050                })?;
1051                let should_save = project_item_ids
1052                    .iter()
1053                    .any(|id| saved_project_items_ids.insert(*id));
1054
1055                if should_save
1056                    && !Self::save_item(
1057                        project.clone(),
1058                        &pane,
1059                        item_ix,
1060                        &*item,
1061                        save_intent,
1062                        &mut cx,
1063                    )
1064                    .await?
1065                {
1066                    break;
1067                }
1068
1069                // Remove the item from the pane.
1070                pane.update(&mut cx, |pane, cx| {
1071                    if let Some(item_ix) = pane
1072                        .items
1073                        .iter()
1074                        .position(|i| i.item_id() == item.item_id())
1075                    {
1076                        pane.remove_item(item_ix, false, cx);
1077                    }
1078                })
1079                .ok();
1080            }
1081
1082            pane.update(&mut cx, |_, cx| cx.notify()).ok();
1083            Ok(())
1084        })
1085    }
1086
1087    pub fn remove_item(
1088        &mut self,
1089        item_index: usize,
1090        activate_pane: bool,
1091        cx: &mut ViewContext<Self>,
1092    ) {
1093        self.activation_history
1094            .retain(|&history_entry| history_entry != self.items[item_index].item_id());
1095
1096        if item_index == self.active_item_index {
1097            let index_to_activate = self
1098                .activation_history
1099                .pop()
1100                .and_then(|last_activated_item| {
1101                    self.items.iter().enumerate().find_map(|(index, item)| {
1102                        (item.item_id() == last_activated_item).then_some(index)
1103                    })
1104                })
1105                // We didn't have a valid activation history entry, so fallback
1106                // to activating the item to the left
1107                .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
1108
1109            let should_activate = activate_pane || self.has_focus(cx);
1110            if self.items.len() == 1 && should_activate {
1111                self.focus_handle.focus(cx);
1112            } else {
1113                self.activate_item(index_to_activate, should_activate, should_activate, cx);
1114            }
1115        }
1116
1117        let item = self.items.remove(item_index);
1118
1119        cx.emit(Event::RemoveItem {
1120            item_id: item.item_id(),
1121        });
1122        if self.items.is_empty() {
1123            item.deactivated(cx);
1124            self.update_toolbar(cx);
1125            cx.emit(Event::Remove);
1126        }
1127
1128        if item_index < self.active_item_index {
1129            self.active_item_index -= 1;
1130        }
1131
1132        self.nav_history.set_mode(NavigationMode::ClosingItem);
1133        item.deactivated(cx);
1134        self.nav_history.set_mode(NavigationMode::Normal);
1135
1136        if let Some(path) = item.project_path(cx) {
1137            let abs_path = self
1138                .nav_history
1139                .0
1140                .lock()
1141                .paths_by_item
1142                .get(&item.item_id())
1143                .and_then(|(_, abs_path)| abs_path.clone());
1144
1145            self.nav_history
1146                .0
1147                .lock()
1148                .paths_by_item
1149                .insert(item.item_id(), (path, abs_path));
1150        } else {
1151            self.nav_history
1152                .0
1153                .lock()
1154                .paths_by_item
1155                .remove(&item.item_id());
1156        }
1157
1158        if self.items.is_empty() && self.zoomed {
1159            cx.emit(Event::ZoomOut);
1160        }
1161
1162        cx.notify();
1163    }
1164
1165    pub async fn save_item(
1166        project: Model<Project>,
1167        pane: &WeakView<Pane>,
1168        item_ix: usize,
1169        item: &dyn ItemHandle,
1170        save_intent: SaveIntent,
1171        cx: &mut AsyncWindowContext,
1172    ) -> Result<bool> {
1173        const CONFLICT_MESSAGE: &str =
1174                "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1175
1176        if save_intent == SaveIntent::Skip {
1177            return Ok(true);
1178        }
1179
1180        let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|_, cx| {
1181            (
1182                item.has_conflict(cx),
1183                item.is_dirty(cx),
1184                item.can_save(cx),
1185                item.is_singleton(cx),
1186            )
1187        })?;
1188
1189        // when saving a single buffer, we ignore whether or not it's dirty.
1190        if save_intent == SaveIntent::Save {
1191            is_dirty = true;
1192        }
1193
1194        if save_intent == SaveIntent::SaveAs {
1195            is_dirty = true;
1196            has_conflict = false;
1197            can_save = false;
1198        }
1199
1200        if save_intent == SaveIntent::Overwrite {
1201            has_conflict = false;
1202        }
1203
1204        if has_conflict && can_save {
1205            let answer = pane.update(cx, |pane, cx| {
1206                pane.activate_item(item_ix, true, true, cx);
1207                cx.prompt(
1208                    PromptLevel::Warning,
1209                    CONFLICT_MESSAGE,
1210                    &["Overwrite", "Discard", "Cancel"],
1211                )
1212            })?;
1213            match answer.await {
1214                Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
1215                Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
1216                _ => return Ok(false),
1217            }
1218        } else if is_dirty && (can_save || can_save_as) {
1219            if save_intent == SaveIntent::Close {
1220                let will_autosave = cx.update(|_, cx| {
1221                    matches!(
1222                        WorkspaceSettings::get_global(cx).autosave,
1223                        AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
1224                    ) && Self::can_autosave_item(&*item, cx)
1225                })?;
1226                if !will_autosave {
1227                    let answer = pane.update(cx, |pane, cx| {
1228                        pane.activate_item(item_ix, true, true, cx);
1229                        let prompt = dirty_message_for(item.project_path(cx));
1230                        cx.prompt(
1231                            PromptLevel::Warning,
1232                            &prompt,
1233                            &["Save", "Don't Save", "Cancel"],
1234                        )
1235                    })?;
1236                    match answer.await {
1237                        Ok(0) => {}
1238                        Ok(1) => return Ok(true), // Don't save this file
1239                        _ => return Ok(false),    // Cancel
1240                    }
1241                }
1242            }
1243
1244            if can_save {
1245                pane.update(cx, |_, cx| item.save(project, cx))?.await?;
1246            } else if can_save_as {
1247                let start_abs_path = project
1248                    .update(cx, |project, cx| {
1249                        let worktree = project.visible_worktrees(cx).next()?;
1250                        Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
1251                    })?
1252                    .unwrap_or_else(|| Path::new("").into());
1253
1254                let abs_path = cx.update(|_, cx| cx.prompt_for_new_path(&start_abs_path))?;
1255                if let Some(abs_path) = abs_path.await.ok().flatten() {
1256                    pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
1257                        .await?;
1258                } else {
1259                    return Ok(false);
1260                }
1261            }
1262        }
1263        Ok(true)
1264    }
1265
1266    fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
1267        let is_deleted = item.project_entry_ids(cx).is_empty();
1268        item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
1269    }
1270
1271    pub fn autosave_item(
1272        item: &dyn ItemHandle,
1273        project: Model<Project>,
1274        cx: &mut WindowContext,
1275    ) -> Task<Result<()>> {
1276        if Self::can_autosave_item(item, cx) {
1277            item.save(project, cx)
1278        } else {
1279            Task::ready(Ok(()))
1280        }
1281    }
1282
1283    pub fn focus(&mut self, cx: &mut ViewContext<Pane>) {
1284        cx.focus(&self.focus_handle);
1285    }
1286
1287    pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
1288        if let Some(active_item) = self.active_item() {
1289            let focus_handle = active_item.focus_handle(cx);
1290            cx.focus(&focus_handle);
1291        }
1292    }
1293
1294    pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
1295        cx.emit(Event::Split(direction));
1296    }
1297
1298    //     fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
1299    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
1300    //             menu.toggle(
1301    //                 Default::default(),
1302    //                 AnchorCorner::TopRight,
1303    //                 vec![
1304    //                     ContextMenuItem::action("Split Right", SplitRight),
1305    //                     ContextMenuItem::action("Split Left", SplitLeft),
1306    //                     ContextMenuItem::action("Split Up", SplitUp),
1307    //                     ContextMenuItem::action("Split Down", SplitDown),
1308    //                 ],
1309    //                 cx,
1310    //             );
1311    //         });
1312
1313    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
1314    //     }
1315
1316    //     fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
1317    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
1318    //             menu.toggle(
1319    //                 Default::default(),
1320    //                 AnchorCorner::TopRight,
1321    //                 vec![
1322    //                     ContextMenuItem::action("New File", NewFile),
1323    //                     ContextMenuItem::action("New Terminal", NewCenterTerminal),
1324    //                     ContextMenuItem::action("New Search", NewSearch),
1325    //                 ],
1326    //                 cx,
1327    //             );
1328    //         });
1329
1330    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
1331    //     }
1332
1333    //     fn deploy_tab_context_menu(
1334    //         &mut self,
1335    //         position: Vector2F,
1336    //         target_item_id: usize,
1337    //         cx: &mut ViewContext<Self>,
1338    //     ) {
1339    //         let active_item_id = self.items[self.active_item_index].id();
1340    //         let is_active_item = target_item_id == active_item_id;
1341    //         let target_pane = cx.weak_handle();
1342
1343    //         // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on.  Currently, 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
1344
1345    //         self.tab_context_menu.update(cx, |menu, cx| {
1346    //             menu.show(
1347    //                 position,
1348    //                 AnchorCorner::TopLeft,
1349    //                 if is_active_item {
1350    //                     vec![
1351    //                         ContextMenuItem::action(
1352    //                             "Close Active Item",
1353    //                             CloseActiveItem { save_intent: None },
1354    //                         ),
1355    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
1356    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
1357    //                         ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
1358    //                         ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
1359    //                         ContextMenuItem::action(
1360    //                             "Close All Items",
1361    //                             CloseAllItems { save_intent: None },
1362    //                         ),
1363    //                     ]
1364    //                 } else {
1365    //                     // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
1366    //                     vec![
1367    //                         ContextMenuItem::handler("Close Inactive Item", {
1368    //                             let pane = target_pane.clone();
1369    //                             move |cx| {
1370    //                                 if let Some(pane) = pane.upgrade(cx) {
1371    //                                     pane.update(cx, |pane, cx| {
1372    //                                         pane.close_item_by_id(
1373    //                                             target_item_id,
1374    //                                             SaveIntent::Close,
1375    //                                             cx,
1376    //                                         )
1377    //                                         .detach_and_log_err(cx);
1378    //                                     })
1379    //                                 }
1380    //                             }
1381    //                         }),
1382    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
1383    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
1384    //                         ContextMenuItem::handler("Close Items To The Left", {
1385    //                             let pane = target_pane.clone();
1386    //                             move |cx| {
1387    //                                 if let Some(pane) = pane.upgrade(cx) {
1388    //                                     pane.update(cx, |pane, cx| {
1389    //                                         pane.close_items_to_the_left_by_id(target_item_id, cx)
1390    //                                             .detach_and_log_err(cx);
1391    //                                     })
1392    //                                 }
1393    //                             }
1394    //                         }),
1395    //                         ContextMenuItem::handler("Close Items To The Right", {
1396    //                             let pane = target_pane.clone();
1397    //                             move |cx| {
1398    //                                 if let Some(pane) = pane.upgrade(cx) {
1399    //                                     pane.update(cx, |pane, cx| {
1400    //                                         pane.close_items_to_the_right_by_id(target_item_id, cx)
1401    //                                             .detach_and_log_err(cx);
1402    //                                     })
1403    //                                 }
1404    //                             }
1405    //                         }),
1406    //                         ContextMenuItem::action(
1407    //                             "Close All Items",
1408    //                             CloseAllItems { save_intent: None },
1409    //                         ),
1410    //                     ]
1411    //                 },
1412    //                 cx,
1413    //             );
1414    //         });
1415    //     }
1416
1417    pub fn toolbar(&self) -> &View<Toolbar> {
1418        &self.toolbar
1419    }
1420
1421    pub fn handle_deleted_project_item(
1422        &mut self,
1423        entry_id: ProjectEntryId,
1424        cx: &mut ViewContext<Pane>,
1425    ) -> Option<()> {
1426        let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
1427            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
1428                Some((i, item.item_id()))
1429            } else {
1430                None
1431            }
1432        })?;
1433
1434        self.remove_item(item_index_to_delete, false, cx);
1435        self.nav_history.remove_item(item_id);
1436
1437        Some(())
1438    }
1439
1440    fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
1441        let active_item = self
1442            .items
1443            .get(self.active_item_index)
1444            .map(|item| item.as_ref());
1445        self.toolbar.update(cx, |toolbar, cx| {
1446            toolbar.set_active_item(active_item, cx);
1447        });
1448    }
1449
1450    fn render_tab(
1451        &self,
1452        ix: usize,
1453        item: &Box<dyn ItemHandle>,
1454        detail: usize,
1455        cx: &mut ViewContext<'_, Pane>,
1456    ) -> impl IntoElement {
1457        let is_active = ix == self.active_item_index;
1458
1459        let label = item.tab_content(Some(detail), is_active, cx);
1460        let close_side = &ItemSettings::get_global(cx).close_position;
1461
1462        let indicator = maybe!({
1463            let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
1464                (true, _) => Color::Warning,
1465                (_, true) => Color::Accent,
1466                (false, false) => return None,
1467            };
1468
1469            Some(Indicator::dot().color(indicator_color))
1470        });
1471
1472        let item_id = item.item_id();
1473        let is_first_item = ix == 0;
1474        let is_last_item = ix == self.items.len() - 1;
1475        let position_relative_to_active_item = ix.cmp(&self.active_item_index);
1476
1477        let tab = Tab::new(ix)
1478            .position(if is_first_item {
1479                TabPosition::First
1480            } else if is_last_item {
1481                TabPosition::Last
1482            } else {
1483                TabPosition::Middle(position_relative_to_active_item)
1484            })
1485            .close_side(match close_side {
1486                ClosePosition::Left => ui::TabCloseSide::Start,
1487                ClosePosition::Right => ui::TabCloseSide::End,
1488            })
1489            .selected(is_active)
1490            .on_click(
1491                cx.listener(move |pane: &mut Self, _, cx| pane.activate_item(ix, true, true, cx)),
1492            )
1493            .on_drag(
1494                DraggedTab {
1495                    pane: cx.view().clone(),
1496                    detail,
1497                    item_id,
1498                    is_active,
1499                    ix,
1500                },
1501                |tab, cx| cx.build_view(|_| tab.clone()),
1502            )
1503            .drag_over::<DraggedTab>(|tab| tab.bg(cx.theme().colors().tab_active_background))
1504            .drag_over::<ProjectEntryId>(|tab| tab.bg(gpui::red()))
1505            .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
1506                this.handle_tab_drop(dragged_tab, ix, cx)
1507            }))
1508            .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| {
1509                this.handle_project_entry_drop(entry_id, cx)
1510            }))
1511            .when_some(item.tab_tooltip_text(cx), |tab, text| {
1512                tab.tooltip(move |cx| Tooltip::text(text.clone(), cx))
1513            })
1514            .start_slot::<Indicator>(indicator)
1515            .end_slot(
1516                IconButton::new("close tab", Icon::Close)
1517                    .icon_color(Color::Muted)
1518                    .size(ButtonSize::None)
1519                    .icon_size(IconSize::XSmall)
1520                    .on_click(cx.listener(move |pane, _, cx| {
1521                        pane.close_item_by_id(item_id, SaveIntent::Close, cx)
1522                            .detach_and_log_err(cx);
1523                    })),
1524            )
1525            .child(label);
1526
1527        let single_entry_to_resolve = {
1528            let item_entries = self.items[ix].project_entry_ids(cx);
1529            if item_entries.len() == 1 {
1530                Some(item_entries[0])
1531            } else {
1532                None
1533            }
1534        };
1535
1536        right_click_menu(ix).trigger(tab).menu(move |cx| {
1537            ContextMenu::build(cx, |menu, _| {
1538                let menu = menu
1539                    .action("Close", CloseActiveItem { save_intent: None }.boxed_clone())
1540                    .action("Close Others", CloseInactiveItems.boxed_clone())
1541                    .separator()
1542                    .action("Close Left", CloseItemsToTheLeft.boxed_clone())
1543                    .action("Close Right", CloseItemsToTheRight.boxed_clone())
1544                    .separator()
1545                    .action("Close Clean", CloseCleanItems.boxed_clone())
1546                    .action(
1547                        "Close All",
1548                        CloseAllItems { save_intent: None }.boxed_clone(),
1549                    );
1550
1551                if let Some(entry) = single_entry_to_resolve {
1552                    menu.separator().action(
1553                        "Reveal In Project Panel",
1554                        RevealInProjectPanel {
1555                            entry_id: entry.to_proto(),
1556                        }
1557                        .boxed_clone(),
1558                    )
1559                } else {
1560                    menu
1561                }
1562            })
1563        })
1564    }
1565
1566    fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement {
1567        TabBar::new("tab_bar")
1568            .track_scroll(self.tab_bar_scroll_handle.clone())
1569            .start_child(
1570                IconButton::new("navigate_backward", Icon::ArrowLeft)
1571                    .icon_size(IconSize::Small)
1572                    .on_click({
1573                        let view = cx.view().clone();
1574                        move |_, cx| view.update(cx, Self::navigate_backward)
1575                    })
1576                    .disabled(!self.can_navigate_backward())
1577                    .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)),
1578            )
1579            .start_child(
1580                IconButton::new("navigate_forward", Icon::ArrowRight)
1581                    .icon_size(IconSize::Small)
1582                    .on_click({
1583                        let view = cx.view().clone();
1584                        move |_, cx| view.update(cx, Self::navigate_backward)
1585                    })
1586                    .disabled(!self.can_navigate_forward())
1587                    .tooltip(|cx| Tooltip::for_action("Go Forward", &GoForward, cx)),
1588            )
1589            .end_child(
1590                div()
1591                    .child(
1592                        IconButton::new("plus", Icon::Plus)
1593                            .icon_size(IconSize::Small)
1594                            .on_click(cx.listener(|this, _, cx| {
1595                                let menu = ContextMenu::build(cx, |menu, _| {
1596                                    menu.action("New File", NewFile.boxed_clone())
1597                                        .action("New Terminal", NewCenterTerminal.boxed_clone())
1598                                        .action("New Search", NewSearch.boxed_clone())
1599                                });
1600                                cx.subscribe(&menu, |this, _, _: &DismissEvent, cx| {
1601                                    this.focus(cx);
1602                                    this.new_item_menu = None;
1603                                })
1604                                .detach();
1605                                this.new_item_menu = Some(menu);
1606                            }))
1607                            .tooltip(|cx| Tooltip::text("New...", cx)),
1608                    )
1609                    .when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
1610                        el.child(Self::render_menu_overlay(new_item_menu))
1611                    }),
1612            )
1613            .end_child(
1614                div()
1615                    .child(
1616                        IconButton::new("split", Icon::Split)
1617                            .icon_size(IconSize::Small)
1618                            .on_click(cx.listener(|this, _, cx| {
1619                                let menu = ContextMenu::build(cx, |menu, _| {
1620                                    menu.action("Split Right", SplitRight.boxed_clone())
1621                                        .action("Split Left", SplitLeft.boxed_clone())
1622                                        .action("Split Up", SplitUp.boxed_clone())
1623                                        .action("Split Down", SplitDown.boxed_clone())
1624                                });
1625                                cx.subscribe(&menu, |this, _, _: &DismissEvent, cx| {
1626                                    this.focus(cx);
1627                                    this.split_item_menu = None;
1628                                })
1629                                .detach();
1630                                this.split_item_menu = Some(menu);
1631                            }))
1632                            .tooltip(|cx| Tooltip::text("Split Pane", cx)),
1633                    )
1634                    .when_some(self.split_item_menu.as_ref(), |el, split_item_menu| {
1635                        el.child(Self::render_menu_overlay(split_item_menu))
1636                    }),
1637            )
1638            .children(
1639                self.items
1640                    .iter()
1641                    .enumerate()
1642                    .zip(self.tab_details(cx))
1643                    .map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
1644            )
1645            .child(
1646                div()
1647                    .min_w_6()
1648                    // HACK: This empty child is currently necessary to force the drop traget to appear
1649                    // despite us setting a min width above.
1650                    .child("")
1651                    .h_full()
1652                    .flex_grow()
1653                    .drag_over::<DraggedTab>(|bar| {
1654                        bar.bg(cx.theme().colors().tab_active_background)
1655                    })
1656                    .drag_over::<ProjectEntryId>(|bar| bar.bg(gpui::red()))
1657                    .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
1658                        this.handle_tab_drop(dragged_tab, this.items.len(), cx)
1659                    }))
1660                    .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| {
1661                        this.handle_project_entry_drop(entry_id, cx)
1662                    })),
1663            )
1664    }
1665
1666    fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
1667        div()
1668            .absolute()
1669            .z_index(1)
1670            .bottom_0()
1671            .right_0()
1672            .size_0()
1673            .child(overlay().anchor(AnchorCorner::TopRight).child(menu.clone()))
1674    }
1675
1676    fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
1677        let mut tab_details = self.items.iter().map(|_| 0).collect::<Vec<_>>();
1678
1679        let mut tab_descriptions = HashMap::default();
1680        let mut done = false;
1681        while !done {
1682            done = true;
1683
1684            // Store item indices by their tab description.
1685            for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
1686                if let Some(description) = item.tab_description(*detail, cx) {
1687                    if *detail == 0
1688                        || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
1689                    {
1690                        tab_descriptions
1691                            .entry(description)
1692                            .or_insert(Vec::new())
1693                            .push(ix);
1694                    }
1695                }
1696            }
1697
1698            // If two or more items have the same tab description, increase eir level
1699            // of detail and try again.
1700            for (_, item_ixs) in tab_descriptions.drain() {
1701                if item_ixs.len() > 1 {
1702                    done = false;
1703                    for ix in item_ixs {
1704                        tab_details[ix] += 1;
1705                    }
1706                }
1707            }
1708        }
1709
1710        tab_details
1711    }
1712
1713    pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1714        self.zoomed = zoomed;
1715        cx.notify();
1716    }
1717
1718    pub fn is_zoomed(&self) -> bool {
1719        self.zoomed
1720    }
1721
1722    fn handle_tab_drop(
1723        &mut self,
1724        dragged_tab: &DraggedTab,
1725        ix: usize,
1726        cx: &mut ViewContext<'_, Pane>,
1727    ) {
1728        let item_id = dragged_tab.item_id;
1729        let from_pane = dragged_tab.pane.clone();
1730        let to_pane = cx.view().clone();
1731        self.workspace
1732            .update(cx, |_, cx| {
1733                cx.defer(move |workspace, cx| {
1734                    workspace.move_item(from_pane, to_pane, item_id, ix, cx);
1735                });
1736            })
1737            .log_err();
1738    }
1739
1740    fn handle_project_entry_drop(
1741        &mut self,
1742        project_entry_id: &ProjectEntryId,
1743        cx: &mut ViewContext<'_, Pane>,
1744    ) {
1745        let to_pane = cx.view().downgrade();
1746        let project_entry_id = *project_entry_id;
1747        self.workspace
1748            .update(cx, |_, cx| {
1749                cx.defer(move |workspace, cx| {
1750                    if let Some(path) = workspace
1751                        .project()
1752                        .read(cx)
1753                        .path_for_entry(project_entry_id, cx)
1754                    {
1755                        workspace
1756                            .open_path(path, Some(to_pane), true, cx)
1757                            .detach_and_log_err(cx);
1758                    }
1759                });
1760            })
1761            .log_err();
1762    }
1763
1764    fn handle_split_tab_drop(
1765        &mut self,
1766        dragged_tab: &DraggedTab,
1767        split_direction: SplitDirection,
1768        cx: &mut ViewContext<'_, Pane>,
1769    ) {
1770        let item_id = dragged_tab.item_id;
1771        let from_pane = dragged_tab.pane.clone();
1772        let to_pane = cx.view().clone();
1773        self.workspace
1774            .update(cx, |_, cx| {
1775                cx.defer(move |workspace, cx| {
1776                    let pane = workspace.split_pane(to_pane, split_direction, cx);
1777                    workspace.move_item(from_pane, pane, item_id, 0, cx);
1778                });
1779            })
1780            .log_err();
1781    }
1782
1783    fn handle_split_project_entry_drop(
1784        &mut self,
1785        project_entry_id: &ProjectEntryId,
1786        split_direction: SplitDirection,
1787        cx: &mut ViewContext<'_, Pane>,
1788    ) {
1789        let project_entry_id = *project_entry_id;
1790        let current_pane = cx.view().clone();
1791        self.workspace
1792            .update(cx, |_, cx| {
1793                cx.defer(move |workspace, cx| {
1794                    if let Some(path) = workspace
1795                        .project()
1796                        .read(cx)
1797                        .path_for_entry(project_entry_id, cx)
1798                    {
1799                        let pane = workspace.split_pane(current_pane, split_direction, cx);
1800                        workspace
1801                            .open_path(path, Some(pane.downgrade()), true, cx)
1802                            .detach_and_log_err(cx);
1803                    }
1804                });
1805            })
1806            .log_err();
1807    }
1808}
1809
1810impl FocusableView for Pane {
1811    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
1812        self.focus_handle.clone()
1813    }
1814}
1815
1816impl Render for Pane {
1817    type Element = Focusable<Div>;
1818
1819    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
1820        v_stack()
1821            .key_context("Pane")
1822            .track_focus(&self.focus_handle)
1823            .size_full()
1824            .flex_none()
1825            .overflow_hidden()
1826            .on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
1827            .on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
1828            .on_action(
1829                cx.listener(|pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)),
1830            )
1831            .on_action(cx.listener(|pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)))
1832            .on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx)))
1833            .on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx)))
1834            .on_action(cx.listener(Pane::toggle_zoom))
1835            .on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
1836                pane.activate_item(action.0, true, true, cx);
1837            }))
1838            .on_action(cx.listener(|pane: &mut Pane, _: &ActivateLastItem, cx| {
1839                pane.activate_item(pane.items.len() - 1, true, true, cx);
1840            }))
1841            .on_action(cx.listener(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
1842                pane.activate_prev_item(true, cx);
1843            }))
1844            .on_action(cx.listener(|pane: &mut Pane, _: &ActivateNextItem, cx| {
1845                pane.activate_next_item(true, cx);
1846            }))
1847            .on_action(
1848                cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
1849                    pane.close_active_item(action, cx)
1850                        .map(|task| task.detach_and_log_err(cx));
1851                }),
1852            )
1853            .on_action(
1854                cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| {
1855                    pane.close_inactive_items(action, cx)
1856                        .map(|task| task.detach_and_log_err(cx));
1857                }),
1858            )
1859            .on_action(
1860                cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| {
1861                    pane.close_clean_items(action, cx)
1862                        .map(|task| task.detach_and_log_err(cx));
1863                }),
1864            )
1865            .on_action(
1866                cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| {
1867                    pane.close_items_to_the_left(action, cx)
1868                        .map(|task| task.detach_and_log_err(cx));
1869                }),
1870            )
1871            .on_action(
1872                cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| {
1873                    pane.close_items_to_the_right(action, cx)
1874                        .map(|task| task.detach_and_log_err(cx));
1875                }),
1876            )
1877            .on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| {
1878                pane.close_all_items(action, cx)
1879                    .map(|task| task.detach_and_log_err(cx));
1880            }))
1881            .on_action(
1882                cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
1883                    pane.close_active_item(action, cx)
1884                        .map(|task| task.detach_and_log_err(cx));
1885                }),
1886            )
1887            .on_action(
1888                cx.listener(|pane: &mut Self, action: &RevealInProjectPanel, cx| {
1889                    pane.project.update(cx, |_, cx| {
1890                        cx.emit(project::Event::RevealInProjectPanel(
1891                            ProjectEntryId::from_proto(action.entry_id),
1892                        ))
1893                    })
1894                }),
1895            )
1896            .child(self.render_tab_bar(cx))
1897            .child(self.toolbar.clone())
1898            .child(if let Some(item) = self.active_item() {
1899                let mut drag_target_color = cx.theme().colors().text;
1900                drag_target_color.a = 0.5;
1901
1902                div()
1903                    .flex()
1904                    .flex_1()
1905                    .relative()
1906                    .child(item.to_any())
1907                    .child(
1908                        div()
1909                            .absolute()
1910                            .full()
1911                            .z_index(1)
1912                            .drag_over::<DraggedTab>(|style| style.bg(drag_target_color))
1913                            .drag_over::<ProjectEntryId>(|style| style.bg(gpui::red()))
1914                            .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
1915                                this.handle_tab_drop(dragged_tab, this.active_item_index(), cx)
1916                            }))
1917                            .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| {
1918                                this.handle_project_entry_drop(entry_id, cx)
1919                            })),
1920                    )
1921                    .children(
1922                        [
1923                            (SplitDirection::Up, 2),
1924                            (SplitDirection::Down, 2),
1925                            (SplitDirection::Left, 3),
1926                            (SplitDirection::Right, 3),
1927                        ]
1928                        .into_iter()
1929                        .map(|(direction, z_index)| {
1930                            let div = div()
1931                                .absolute()
1932                                .z_index(z_index)
1933                                .invisible()
1934                                .bg(drag_target_color)
1935                                .drag_over::<DraggedTab>(|style| style.visible())
1936                                .drag_over::<ProjectEntryId>(|style| style.visible())
1937                                .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
1938                                    this.handle_split_tab_drop(dragged_tab, direction, cx)
1939                                }))
1940                                .on_drop(cx.listener(
1941                                    move |this, entry_id: &ProjectEntryId, cx| {
1942                                        this.handle_split_project_entry_drop(
1943                                            entry_id, direction, cx,
1944                                        )
1945                                    },
1946                                ));
1947                            match direction {
1948                                SplitDirection::Up => div.top_0().left_0().right_0().h_32(),
1949                                SplitDirection::Down => div.left_0().bottom_0().right_0().h_32(),
1950                                SplitDirection::Left => div.top_0().left_0().bottom_0().w_32(),
1951                                SplitDirection::Right => div.top_0().bottom_0().right_0().w_32(),
1952                            }
1953                        }),
1954                    )
1955            } else {
1956                h_stack()
1957                    .items_center()
1958                    .size_full()
1959                    .justify_center()
1960                    .child(Label::new("Open a file or project to get started.").color(Color::Muted))
1961            })
1962            .on_mouse_down(
1963                MouseButton::Navigate(NavigationDirection::Back),
1964                cx.listener(|pane, _, cx| {
1965                    if let Some(workspace) = pane.workspace.upgrade() {
1966                        let pane = cx.view().downgrade();
1967                        cx.window_context().defer(move |cx| {
1968                            workspace.update(cx, |workspace, cx| {
1969                                workspace.go_back(pane, cx).detach_and_log_err(cx)
1970                            })
1971                        })
1972                    }
1973                }),
1974            )
1975            .on_mouse_down(
1976                MouseButton::Navigate(NavigationDirection::Forward),
1977                cx.listener(|pane, _, cx| {
1978                    if let Some(workspace) = pane.workspace.upgrade() {
1979                        let pane = cx.view().downgrade();
1980                        cx.window_context().defer(move |cx| {
1981                            workspace.update(cx, |workspace, cx| {
1982                                workspace.go_forward(pane, cx).detach_and_log_err(cx)
1983                            })
1984                        })
1985                    }
1986                }),
1987            )
1988    }
1989
1990    // fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
1991    //     if !self.has_focus {
1992    //         self.has_focus = true;
1993    //         cx.emit(Event::Focus);
1994    //         cx.notify();
1995    //     }
1996
1997    //     self.toolbar.update(cx, |toolbar, cx| {
1998    //         toolbar.focus_changed(true, cx);
1999    //     });
2000
2001    //     if let Some(active_item) = self.active_item() {
2002    //         if cx.is_self_focused() {
2003    //             // Pane was focused directly. We need to either focus a view inside the active item,
2004    //             // or focus the active item itself
2005    //             if let Some(weak_last_focused_view) =
2006    //                 self.last_focused_view_by_item.get(&active_item.id())
2007    //             {
2008    //                 if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
2009    //                     cx.focus(&last_focused_view);
2010    //                     return;
2011    //                 } else {
2012    //                     self.last_focused_view_by_item.remove(&active_item.id());
2013    //                 }
2014    //             }
2015
2016    //             cx.focus(active_item.as_any());
2017    //         } else if focused != self.tab_bar_context_menu.handle {
2018    //             self.last_focused_view_by_item
2019    //                 .insert(active_item.id(), focused.downgrade());
2020    //         }
2021    //     }
2022    // }
2023
2024    // fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
2025    //     self.has_focus = false;
2026    //     self.toolbar.update(cx, |toolbar, cx| {
2027    //         toolbar.focus_changed(false, cx);
2028    //     });
2029    //     cx.notify();
2030    // }
2031
2032    // fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
2033    //     Self::reset_to_default_keymap_context(keymap);
2034    // }
2035}
2036
2037impl ItemNavHistory {
2038    pub fn push<D: 'static + Send + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
2039        self.history.push(data, self.item.clone(), cx);
2040    }
2041
2042    pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
2043        self.history.pop(NavigationMode::GoingBack, cx)
2044    }
2045
2046    pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
2047        self.history.pop(NavigationMode::GoingForward, cx)
2048    }
2049}
2050
2051impl NavHistory {
2052    pub fn for_each_entry(
2053        &self,
2054        cx: &AppContext,
2055        mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
2056    ) {
2057        let borrowed_history = self.0.lock();
2058        borrowed_history
2059            .forward_stack
2060            .iter()
2061            .chain(borrowed_history.backward_stack.iter())
2062            .chain(borrowed_history.closed_stack.iter())
2063            .for_each(|entry| {
2064                if let Some(project_and_abs_path) =
2065                    borrowed_history.paths_by_item.get(&entry.item.id())
2066                {
2067                    f(entry, project_and_abs_path.clone());
2068                } else if let Some(item) = entry.item.upgrade() {
2069                    if let Some(path) = item.project_path(cx) {
2070                        f(entry, (path, None));
2071                    }
2072                }
2073            })
2074    }
2075
2076    pub fn set_mode(&mut self, mode: NavigationMode) {
2077        self.0.lock().mode = mode;
2078    }
2079
2080    pub fn mode(&self) -> NavigationMode {
2081        self.0.lock().mode
2082    }
2083
2084    pub fn disable(&mut self) {
2085        self.0.lock().mode = NavigationMode::Disabled;
2086    }
2087
2088    pub fn enable(&mut self) {
2089        self.0.lock().mode = NavigationMode::Normal;
2090    }
2091
2092    pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
2093        let mut state = self.0.lock();
2094        let entry = match mode {
2095            NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
2096                return None
2097            }
2098            NavigationMode::GoingBack => &mut state.backward_stack,
2099            NavigationMode::GoingForward => &mut state.forward_stack,
2100            NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
2101        }
2102        .pop_back();
2103        if entry.is_some() {
2104            state.did_update(cx);
2105        }
2106        entry
2107    }
2108
2109    pub fn push<D: 'static + Send + Any>(
2110        &mut self,
2111        data: Option<D>,
2112        item: Arc<dyn WeakItemHandle>,
2113        cx: &mut WindowContext,
2114    ) {
2115        let state = &mut *self.0.lock();
2116        match state.mode {
2117            NavigationMode::Disabled => {}
2118            NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
2119                if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2120                    state.backward_stack.pop_front();
2121                }
2122                state.backward_stack.push_back(NavigationEntry {
2123                    item,
2124                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2125                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2126                });
2127                state.forward_stack.clear();
2128            }
2129            NavigationMode::GoingBack => {
2130                if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2131                    state.forward_stack.pop_front();
2132                }
2133                state.forward_stack.push_back(NavigationEntry {
2134                    item,
2135                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2136                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2137                });
2138            }
2139            NavigationMode::GoingForward => {
2140                if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2141                    state.backward_stack.pop_front();
2142                }
2143                state.backward_stack.push_back(NavigationEntry {
2144                    item,
2145                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2146                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2147                });
2148            }
2149            NavigationMode::ClosingItem => {
2150                if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2151                    state.closed_stack.pop_front();
2152                }
2153                state.closed_stack.push_back(NavigationEntry {
2154                    item,
2155                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2156                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2157                });
2158            }
2159        }
2160        state.did_update(cx);
2161    }
2162
2163    pub fn remove_item(&mut self, item_id: EntityId) {
2164        let mut state = self.0.lock();
2165        state.paths_by_item.remove(&item_id);
2166        state
2167            .backward_stack
2168            .retain(|entry| entry.item.id() != item_id);
2169        state
2170            .forward_stack
2171            .retain(|entry| entry.item.id() != item_id);
2172        state
2173            .closed_stack
2174            .retain(|entry| entry.item.id() != item_id);
2175    }
2176
2177    pub fn path_for_item(&self, item_id: EntityId) -> Option<(ProjectPath, Option<PathBuf>)> {
2178        self.0.lock().paths_by_item.get(&item_id).cloned()
2179    }
2180}
2181
2182impl NavHistoryState {
2183    pub fn did_update(&self, cx: &mut WindowContext) {
2184        if let Some(pane) = self.pane.upgrade() {
2185            cx.defer(move |cx| {
2186                pane.update(cx, |pane, cx| pane.history_updated(cx));
2187            });
2188        }
2189    }
2190}
2191
2192// pub struct PaneBackdrop<V> {
2193//     child_view: usize,
2194//     child: AnyElement<V>,
2195// }
2196
2197// impl<V> PaneBackdrop<V> {
2198//     pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
2199//         PaneBackdrop {
2200//             child,
2201//             child_view: pane_item_view,
2202//         }
2203//     }
2204// }
2205
2206// impl<V: 'static> Element<V> for PaneBackdrop<V> {
2207//     type LayoutState = ();
2208
2209//     type PaintState = ();
2210
2211//     fn layout(
2212//         &mut self,
2213//         constraint: gpui::SizeConstraint,
2214//         view: &mut V,
2215//         cx: &mut ViewContext<V>,
2216//     ) -> (Vector2F, Self::LayoutState) {
2217//         let size = self.child.layout(constraint, view, cx);
2218//         (size, ())
2219//     }
2220
2221//     fn paint(
2222//         &mut self,
2223//         bounds: RectF,
2224//         visible_bounds: RectF,
2225//         _: &mut Self::LayoutState,
2226//         view: &mut V,
2227//         cx: &mut ViewContext<V>,
2228//     ) -> Self::PaintState {
2229//         let background = theme::current(cx).editor.background;
2230
2231//         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
2232
2233//         cx.scene().push_quad(gpui::Quad {
2234//             bounds: RectF::new(bounds.origin(), bounds.size()),
2235//             background: Some(background),
2236//             ..Default::default()
2237//         });
2238
2239//         let child_view_id = self.child_view;
2240//         cx.scene().push_mouse_region(
2241//             MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
2242//                 gpui::platform::MouseButton::Left,
2243//                 move |_, _: &mut V, cx| {
2244//                     let window = cx.window();
2245//                     cx.app_context().focus(window, Some(child_view_id))
2246//                 },
2247//             ),
2248//         );
2249
2250//         cx.scene().push_layer(Some(bounds));
2251//         self.child.paint(bounds.origin(), visible_bounds, view, cx);
2252//         cx.scene().pop_layer();
2253//     }
2254
2255//     fn rect_for_text_range(
2256//         &self,
2257//         range_utf16: std::ops::Range<usize>,
2258//         _bounds: RectF,
2259//         _visible_bounds: RectF,
2260//         _layout: &Self::LayoutState,
2261//         _paint: &Self::PaintState,
2262//         view: &V,
2263//         cx: &gpui::ViewContext<V>,
2264//     ) -> Option<RectF> {
2265//         self.child.rect_for_text_range(range_utf16, view, cx)
2266//     }
2267
2268//     fn debug(
2269//         &self,
2270//         _bounds: RectF,
2271//         _layout: &Self::LayoutState,
2272//         _paint: &Self::PaintState,
2273//         view: &V,
2274//         cx: &gpui::ViewContext<V>,
2275//     ) -> serde_json::Value {
2276//         gpui::json::json!({
2277//             "type": "Pane Back Drop",
2278//             "view": self.child_view,
2279//             "child": self.child.debug(view, cx),
2280//         })
2281//     }
2282// }
2283
2284fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
2285    let path = buffer_path
2286        .as_ref()
2287        .and_then(|p| p.path.to_str())
2288        .unwrap_or(&"This buffer");
2289    let path = truncate_and_remove_front(path, 80);
2290    format!("{path} contains unsaved edits. Do you want to save it?")
2291}
2292
2293// todo!("uncomment tests")
2294// #[cfg(test)]
2295// mod tests {
2296//     use super::*;
2297//     use crate::item::test::{TestItem, TestProjectItem};
2298//     use gpui::TestAppContext;
2299//     use project::FakeFs;
2300//     use settings::SettingsStore;
2301
2302//     #[gpui::test]
2303//     async fn test_remove_active_empty(cx: &mut TestAppContext) {
2304//         init_test(cx);
2305//         let fs = FakeFs::new(cx.background());
2306
2307//         let project = Project::test(fs, None, cx).await;
2308//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2309//         let workspace = window.root(cx);
2310//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2311
2312//         pane.update(cx, |pane, cx| {
2313//             assert!(pane
2314//                 .close_active_item(&CloseActiveItem { save_intent: None }, cx)
2315//                 .is_none())
2316//         });
2317//     }
2318
2319//     #[gpui::test]
2320//     async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
2321//         cx.foreground().forbid_parking();
2322//         init_test(cx);
2323//         let fs = FakeFs::new(cx.background());
2324
2325//         let project = Project::test(fs, None, cx).await;
2326//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2327//         let workspace = window.root(cx);
2328//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2329
2330//         // 1. Add with a destination index
2331//         //   a. Add before the active item
2332//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
2333//         pane.update(cx, |pane, cx| {
2334//             pane.add_item(
2335//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2336//                 false,
2337//                 false,
2338//                 Some(0),
2339//                 cx,
2340//             );
2341//         });
2342//         assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2343
2344//         //   b. Add after the active item
2345//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
2346//         pane.update(cx, |pane, cx| {
2347//             pane.add_item(
2348//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2349//                 false,
2350//                 false,
2351//                 Some(2),
2352//                 cx,
2353//             );
2354//         });
2355//         assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2356
2357//         //   c. Add at the end of the item list (including off the length)
2358//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
2359//         pane.update(cx, |pane, cx| {
2360//             pane.add_item(
2361//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2362//                 false,
2363//                 false,
2364//                 Some(5),
2365//                 cx,
2366//             );
2367//         });
2368//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2369
2370//         // 2. Add without a destination index
2371//         //   a. Add with active item at the start of the item list
2372//         set_labeled_items(&pane, ["A*", "B", "C"], cx);
2373//         pane.update(cx, |pane, cx| {
2374//             pane.add_item(
2375//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2376//                 false,
2377//                 false,
2378//                 None,
2379//                 cx,
2380//             );
2381//         });
2382//         set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
2383
2384//         //   b. Add with active item at the end of the item list
2385//         set_labeled_items(&pane, ["A", "B", "C*"], cx);
2386//         pane.update(cx, |pane, cx| {
2387//             pane.add_item(
2388//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2389//                 false,
2390//                 false,
2391//                 None,
2392//                 cx,
2393//             );
2394//         });
2395//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2396//     }
2397
2398//     #[gpui::test]
2399//     async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
2400//         cx.foreground().forbid_parking();
2401//         init_test(cx);
2402//         let fs = FakeFs::new(cx.background());
2403
2404//         let project = Project::test(fs, None, cx).await;
2405//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2406//         let workspace = window.root(cx);
2407//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2408
2409//         // 1. Add with a destination index
2410//         //   1a. Add before the active item
2411//         let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2412//         pane.update(cx, |pane, cx| {
2413//             pane.add_item(d, false, false, Some(0), cx);
2414//         });
2415//         assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2416
2417//         //   1b. Add after the active item
2418//         let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2419//         pane.update(cx, |pane, cx| {
2420//             pane.add_item(d, false, false, Some(2), cx);
2421//         });
2422//         assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2423
2424//         //   1c. Add at the end of the item list (including off the length)
2425//         let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2426//         pane.update(cx, |pane, cx| {
2427//             pane.add_item(a, false, false, Some(5), cx);
2428//         });
2429//         assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2430
2431//         //   1d. Add same item to active index
2432//         let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
2433//         pane.update(cx, |pane, cx| {
2434//             pane.add_item(b, false, false, Some(1), cx);
2435//         });
2436//         assert_item_labels(&pane, ["A", "B*", "C"], cx);
2437
2438//         //   1e. Add item to index after same item in last position
2439//         let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
2440//         pane.update(cx, |pane, cx| {
2441//             pane.add_item(c, false, false, Some(2), cx);
2442//         });
2443//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
2444
2445//         // 2. Add without a destination index
2446//         //   2a. Add with active item at the start of the item list
2447//         let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
2448//         pane.update(cx, |pane, cx| {
2449//             pane.add_item(d, false, false, None, cx);
2450//         });
2451//         assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
2452
2453//         //   2b. Add with active item at the end of the item list
2454//         let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
2455//         pane.update(cx, |pane, cx| {
2456//             pane.add_item(a, false, false, None, cx);
2457//         });
2458//         assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2459
2460//         //   2c. Add active item to active item at end of list
2461//         let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
2462//         pane.update(cx, |pane, cx| {
2463//             pane.add_item(c, false, false, None, cx);
2464//         });
2465//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
2466
2467//         //   2d. Add active item to active item at start of list
2468//         let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
2469//         pane.update(cx, |pane, cx| {
2470//             pane.add_item(a, false, false, None, cx);
2471//         });
2472//         assert_item_labels(&pane, ["A*", "B", "C"], cx);
2473//     }
2474
2475//     #[gpui::test]
2476//     async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
2477//         cx.foreground().forbid_parking();
2478//         init_test(cx);
2479//         let fs = FakeFs::new(cx.background());
2480
2481//         let project = Project::test(fs, None, cx).await;
2482//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2483//         let workspace = window.root(cx);
2484//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2485
2486//         // singleton view
2487//         pane.update(cx, |pane, cx| {
2488//             let item = TestItem::new()
2489//                 .with_singleton(true)
2490//                 .with_label("buffer 1")
2491//                 .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
2492
2493//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2494//         });
2495//         assert_item_labels(&pane, ["buffer 1*"], cx);
2496
2497//         // new singleton view with the same project entry
2498//         pane.update(cx, |pane, cx| {
2499//             let item = TestItem::new()
2500//                 .with_singleton(true)
2501//                 .with_label("buffer 1")
2502//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2503
2504//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2505//         });
2506//         assert_item_labels(&pane, ["buffer 1*"], cx);
2507
2508//         // new singleton view with different project entry
2509//         pane.update(cx, |pane, cx| {
2510//             let item = TestItem::new()
2511//                 .with_singleton(true)
2512//                 .with_label("buffer 2")
2513//                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
2514//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2515//         });
2516//         assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
2517
2518//         // new multibuffer view with the same project entry
2519//         pane.update(cx, |pane, cx| {
2520//             let item = TestItem::new()
2521//                 .with_singleton(false)
2522//                 .with_label("multibuffer 1")
2523//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2524
2525//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2526//         });
2527//         assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
2528
2529//         // another multibuffer view with the same project entry
2530//         pane.update(cx, |pane, cx| {
2531//             let item = TestItem::new()
2532//                 .with_singleton(false)
2533//                 .with_label("multibuffer 1b")
2534//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2535
2536//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2537//         });
2538//         assert_item_labels(
2539//             &pane,
2540//             ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
2541//             cx,
2542//         );
2543//     }
2544
2545//     #[gpui::test]
2546//     async fn test_remove_item_ordering(cx: &mut TestAppContext) {
2547//         init_test(cx);
2548//         let fs = FakeFs::new(cx.background());
2549
2550//         let project = Project::test(fs, None, cx).await;
2551//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2552//         let workspace = window.root(cx);
2553//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2554
2555//         add_labeled_item(&pane, "A", false, cx);
2556//         add_labeled_item(&pane, "B", false, cx);
2557//         add_labeled_item(&pane, "C", false, cx);
2558//         add_labeled_item(&pane, "D", false, cx);
2559//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2560
2561//         pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
2562//         add_labeled_item(&pane, "1", false, cx);
2563//         assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
2564
2565//         pane.update(cx, |pane, cx| {
2566//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2567//         })
2568//         .unwrap()
2569//         .await
2570//         .unwrap();
2571//         assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
2572
2573//         pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
2574//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2575
2576//         pane.update(cx, |pane, cx| {
2577//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2578//         })
2579//         .unwrap()
2580//         .await
2581//         .unwrap();
2582//         assert_item_labels(&pane, ["A", "B*", "C"], cx);
2583
2584//         pane.update(cx, |pane, cx| {
2585//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2586//         })
2587//         .unwrap()
2588//         .await
2589//         .unwrap();
2590//         assert_item_labels(&pane, ["A", "C*"], cx);
2591
2592//         pane.update(cx, |pane, cx| {
2593//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2594//         })
2595//         .unwrap()
2596//         .await
2597//         .unwrap();
2598//         assert_item_labels(&pane, ["A*"], cx);
2599//     }
2600
2601//     #[gpui::test]
2602//     async fn test_close_inactive_items(cx: &mut TestAppContext) {
2603//         init_test(cx);
2604//         let fs = FakeFs::new(cx.background());
2605
2606//         let project = Project::test(fs, None, cx).await;
2607//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2608//         let workspace = window.root(cx);
2609//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2610
2611//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2612
2613//         pane.update(cx, |pane, cx| {
2614//             pane.close_inactive_items(&CloseInactiveItems, cx)
2615//         })
2616//         .unwrap()
2617//         .await
2618//         .unwrap();
2619//         assert_item_labels(&pane, ["C*"], cx);
2620//     }
2621
2622//     #[gpui::test]
2623//     async fn test_close_clean_items(cx: &mut TestAppContext) {
2624//         init_test(cx);
2625//         let fs = FakeFs::new(cx.background());
2626
2627//         let project = Project::test(fs, None, cx).await;
2628//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2629//         let workspace = window.root(cx);
2630//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2631
2632//         add_labeled_item(&pane, "A", true, cx);
2633//         add_labeled_item(&pane, "B", false, cx);
2634//         add_labeled_item(&pane, "C", true, cx);
2635//         add_labeled_item(&pane, "D", false, cx);
2636//         add_labeled_item(&pane, "E", false, cx);
2637//         assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
2638
2639//         pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
2640//             .unwrap()
2641//             .await
2642//             .unwrap();
2643//         assert_item_labels(&pane, ["A^", "C*^"], cx);
2644//     }
2645
2646//     #[gpui::test]
2647//     async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
2648//         init_test(cx);
2649//         let fs = FakeFs::new(cx.background());
2650
2651//         let project = Project::test(fs, None, cx).await;
2652//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2653//         let workspace = window.root(cx);
2654//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2655
2656//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2657
2658//         pane.update(cx, |pane, cx| {
2659//             pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
2660//         })
2661//         .unwrap()
2662//         .await
2663//         .unwrap();
2664//         assert_item_labels(&pane, ["C*", "D", "E"], cx);
2665//     }
2666
2667//     #[gpui::test]
2668//     async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
2669//         init_test(cx);
2670//         let fs = FakeFs::new(cx.background());
2671
2672//         let project = Project::test(fs, None, cx).await;
2673//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2674//         let workspace = window.root(cx);
2675//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2676
2677//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2678
2679//         pane.update(cx, |pane, cx| {
2680//             pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
2681//         })
2682//         .unwrap()
2683//         .await
2684//         .unwrap();
2685//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
2686//     }
2687
2688//     #[gpui::test]
2689//     async fn test_close_all_items(cx: &mut TestAppContext) {
2690//         init_test(cx);
2691//         let fs = FakeFs::new(cx.background());
2692
2693//         let project = Project::test(fs, None, cx).await;
2694//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2695//         let workspace = window.root(cx);
2696//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2697
2698//         add_labeled_item(&pane, "A", false, cx);
2699//         add_labeled_item(&pane, "B", false, cx);
2700//         add_labeled_item(&pane, "C", false, cx);
2701//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
2702
2703//         pane.update(cx, |pane, cx| {
2704//             pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
2705//         })
2706//         .unwrap()
2707//         .await
2708//         .unwrap();
2709//         assert_item_labels(&pane, [], cx);
2710
2711//         add_labeled_item(&pane, "A", true, cx);
2712//         add_labeled_item(&pane, "B", true, cx);
2713//         add_labeled_item(&pane, "C", true, cx);
2714//         assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
2715
2716//         let save = pane
2717//             .update(cx, |pane, cx| {
2718//                 pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
2719//             })
2720//             .unwrap();
2721
2722//         cx.foreground().run_until_parked();
2723//         window.simulate_prompt_answer(2, cx);
2724//         save.await.unwrap();
2725//         assert_item_labels(&pane, [], cx);
2726//     }
2727
2728//     fn init_test(cx: &mut TestAppContext) {
2729//         cx.update(|cx| {
2730//             cx.set_global(SettingsStore::test(cx));
2731//             theme::init((), cx);
2732//             crate::init_settings(cx);
2733//             Project::init_settings(cx);
2734//         });
2735//     }
2736
2737//     fn add_labeled_item(
2738//         pane: &ViewHandle<Pane>,
2739//         label: &str,
2740//         is_dirty: bool,
2741//         cx: &mut TestAppContext,
2742//     ) -> Box<ViewHandle<TestItem>> {
2743//         pane.update(cx, |pane, cx| {
2744//             let labeled_item =
2745//                 Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
2746//             pane.add_item(labeled_item.clone(), false, false, None, cx);
2747//             labeled_item
2748//         })
2749//     }
2750
2751//     fn set_labeled_items<const COUNT: usize>(
2752//         pane: &ViewHandle<Pane>,
2753//         labels: [&str; COUNT],
2754//         cx: &mut TestAppContext,
2755//     ) -> [Box<ViewHandle<TestItem>>; COUNT] {
2756//         pane.update(cx, |pane, cx| {
2757//             pane.items.clear();
2758//             let mut active_item_index = 0;
2759
2760//             let mut index = 0;
2761//             let items = labels.map(|mut label| {
2762//                 if label.ends_with("*") {
2763//                     label = label.trim_end_matches("*");
2764//                     active_item_index = index;
2765//                 }
2766
2767//                 let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
2768//                 pane.add_item(labeled_item.clone(), false, false, None, cx);
2769//                 index += 1;
2770//                 labeled_item
2771//             });
2772
2773//             pane.activate_item(active_item_index, false, false, cx);
2774
2775//             items
2776//         })
2777//     }
2778
2779//     // Assert the item label, with the active item label suffixed with a '*'
2780//     fn assert_item_labels<const COUNT: usize>(
2781//         pane: &ViewHandle<Pane>,
2782//         expected_states: [&str; COUNT],
2783//         cx: &mut TestAppContext,
2784//     ) {
2785//         pane.read_with(cx, |pane, cx| {
2786//             let actual_states = pane
2787//                 .items
2788//                 .iter()
2789//                 .enumerate()
2790//                 .map(|(ix, item)| {
2791//                     let mut state = item
2792//                         .as_any()
2793//                         .downcast_ref::<TestItem>()
2794//                         .unwrap()
2795//                         .read(cx)
2796//                         .label
2797//                         .clone();
2798//                     if ix == pane.active_item_index {
2799//                         state.push('*');
2800//                     }
2801//                     if item.is_dirty(cx) {
2802//                         state.push('^');
2803//                     }
2804//                     state
2805//                 })
2806//                 .collect::<Vec<_>>();
2807
2808//             assert_eq!(
2809//                 actual_states, expected_states,
2810//                 "pane items do not match expectation"
2811//             );
2812//         })
2813//     }
2814// }
2815
2816impl Render for DraggedTab {
2817    type Element = <Tab as RenderOnce>::Rendered;
2818
2819    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
2820        let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
2821        let item = &self.pane.read(cx).items[self.ix];
2822        let label = item.tab_content(Some(self.detail), false, cx);
2823        Tab::new("")
2824            .selected(self.is_active)
2825            .child(label)
2826            .render(cx)
2827            .font(ui_font)
2828    }
2829}