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