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            }
1051
1052            pane.update(&mut cx, |_, cx| cx.notify())?;
1053            Ok(())
1054        })
1055    }
1056
1057    pub fn remove_item(
1058        &mut self,
1059        item_index: usize,
1060        activate_pane: bool,
1061        cx: &mut ViewContext<Self>,
1062    ) {
1063        self.activation_history
1064            .retain(|&history_entry| history_entry != self.items[item_index].item_id());
1065
1066        if item_index == self.active_item_index {
1067            let index_to_activate = self
1068                .activation_history
1069                .pop()
1070                .and_then(|last_activated_item| {
1071                    self.items.iter().enumerate().find_map(|(index, item)| {
1072                        (item.item_id() == last_activated_item).then_some(index)
1073                    })
1074                })
1075                // We didn't have a valid activation history entry, so fallback
1076                // to activating the item to the left
1077                .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
1078
1079            let should_activate = activate_pane || self.has_focus(cx);
1080            if self.items.len() == 1 && should_activate {
1081                self.focus_handle.focus(cx);
1082            } else {
1083                self.activate_item(index_to_activate, should_activate, should_activate, cx);
1084            }
1085        }
1086
1087        let item = self.items.remove(item_index);
1088
1089        cx.emit(Event::RemoveItem {
1090            item_id: item.item_id(),
1091        });
1092        if self.items.is_empty() {
1093            item.deactivated(cx);
1094            self.update_toolbar(cx);
1095            cx.emit(Event::Remove);
1096        }
1097
1098        if item_index < self.active_item_index {
1099            self.active_item_index -= 1;
1100        }
1101
1102        self.nav_history.set_mode(NavigationMode::ClosingItem);
1103        item.deactivated(cx);
1104        self.nav_history.set_mode(NavigationMode::Normal);
1105
1106        if let Some(path) = item.project_path(cx) {
1107            let abs_path = self
1108                .nav_history
1109                .0
1110                .lock()
1111                .paths_by_item
1112                .get(&item.item_id())
1113                .and_then(|(_, abs_path)| abs_path.clone());
1114
1115            self.nav_history
1116                .0
1117                .lock()
1118                .paths_by_item
1119                .insert(item.item_id(), (path, abs_path));
1120        } else {
1121            self.nav_history
1122                .0
1123                .lock()
1124                .paths_by_item
1125                .remove(&item.item_id());
1126        }
1127
1128        if self.items.is_empty() && self.zoomed {
1129            cx.emit(Event::ZoomOut);
1130        }
1131
1132        cx.notify();
1133    }
1134
1135    pub async fn save_item(
1136        project: Model<Project>,
1137        pane: &WeakView<Pane>,
1138        item_ix: usize,
1139        item: &dyn ItemHandle,
1140        save_intent: SaveIntent,
1141        cx: &mut AsyncWindowContext,
1142    ) -> Result<bool> {
1143        const CONFLICT_MESSAGE: &str =
1144                "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1145
1146        if save_intent == SaveIntent::Skip {
1147            return Ok(true);
1148        }
1149
1150        let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|_, cx| {
1151            (
1152                item.has_conflict(cx),
1153                item.is_dirty(cx),
1154                item.can_save(cx),
1155                item.is_singleton(cx),
1156            )
1157        })?;
1158
1159        // when saving a single buffer, we ignore whether or not it's dirty.
1160        if save_intent == SaveIntent::Save {
1161            is_dirty = true;
1162        }
1163
1164        if save_intent == SaveIntent::SaveAs {
1165            is_dirty = true;
1166            has_conflict = false;
1167            can_save = false;
1168        }
1169
1170        if save_intent == SaveIntent::Overwrite {
1171            has_conflict = false;
1172        }
1173
1174        if has_conflict && can_save {
1175            let answer = pane.update(cx, |pane, cx| {
1176                pane.activate_item(item_ix, true, true, cx);
1177                cx.prompt(
1178                    PromptLevel::Warning,
1179                    CONFLICT_MESSAGE,
1180                    &["Overwrite", "Discard", "Cancel"],
1181                )
1182            })?;
1183            match answer.await {
1184                Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
1185                Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
1186                _ => return Ok(false),
1187            }
1188        } else if is_dirty && (can_save || can_save_as) {
1189            if save_intent == SaveIntent::Close {
1190                let will_autosave = cx.update(|_, cx| {
1191                    matches!(
1192                        WorkspaceSettings::get_global(cx).autosave,
1193                        AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
1194                    ) && Self::can_autosave_item(&*item, cx)
1195                })?;
1196                if !will_autosave {
1197                    let answer = pane.update(cx, |pane, cx| {
1198                        pane.activate_item(item_ix, true, true, cx);
1199                        let prompt = dirty_message_for(item.project_path(cx));
1200                        cx.prompt(
1201                            PromptLevel::Warning,
1202                            &prompt,
1203                            &["Save", "Don't Save", "Cancel"],
1204                        )
1205                    })?;
1206                    match answer.await {
1207                        Ok(0) => {}
1208                        Ok(1) => return Ok(true), // Don't save this file
1209                        _ => return Ok(false),    // Cancel
1210                    }
1211                }
1212            }
1213
1214            if can_save {
1215                pane.update(cx, |_, cx| item.save(project, cx))?.await?;
1216            } else if can_save_as {
1217                let start_abs_path = project
1218                    .update(cx, |project, cx| {
1219                        let worktree = project.visible_worktrees(cx).next()?;
1220                        Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
1221                    })?
1222                    .unwrap_or_else(|| Path::new("").into());
1223
1224                let abs_path = cx.update(|_, cx| cx.prompt_for_new_path(&start_abs_path))?;
1225                if let Some(abs_path) = abs_path.await.ok().flatten() {
1226                    pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
1227                        .await?;
1228                } else {
1229                    return Ok(false);
1230                }
1231            }
1232        }
1233        Ok(true)
1234    }
1235
1236    fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
1237        let is_deleted = item.project_entry_ids(cx).is_empty();
1238        item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
1239    }
1240
1241    pub fn autosave_item(
1242        item: &dyn ItemHandle,
1243        project: Model<Project>,
1244        cx: &mut WindowContext,
1245    ) -> Task<Result<()>> {
1246        if Self::can_autosave_item(item, cx) {
1247            item.save(project, cx)
1248        } else {
1249            Task::ready(Ok(()))
1250        }
1251    }
1252
1253    pub fn focus(&mut self, cx: &mut ViewContext<Pane>) {
1254        cx.focus(&self.focus_handle);
1255    }
1256
1257    pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
1258        if let Some(active_item) = self.active_item() {
1259            let focus_handle = active_item.focus_handle(cx);
1260            cx.focus(&focus_handle);
1261        }
1262    }
1263
1264    pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
1265        cx.emit(Event::Split(direction));
1266    }
1267
1268    //     fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
1269    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
1270    //             menu.toggle(
1271    //                 Default::default(),
1272    //                 AnchorCorner::TopRight,
1273    //                 vec![
1274    //                     ContextMenuItem::action("Split Right", SplitRight),
1275    //                     ContextMenuItem::action("Split Left", SplitLeft),
1276    //                     ContextMenuItem::action("Split Up", SplitUp),
1277    //                     ContextMenuItem::action("Split Down", SplitDown),
1278    //                 ],
1279    //                 cx,
1280    //             );
1281    //         });
1282
1283    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
1284    //     }
1285
1286    //     fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
1287    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
1288    //             menu.toggle(
1289    //                 Default::default(),
1290    //                 AnchorCorner::TopRight,
1291    //                 vec![
1292    //                     ContextMenuItem::action("New File", NewFile),
1293    //                     ContextMenuItem::action("New Terminal", NewCenterTerminal),
1294    //                     ContextMenuItem::action("New Search", NewSearch),
1295    //                 ],
1296    //                 cx,
1297    //             );
1298    //         });
1299
1300    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
1301    //     }
1302
1303    //     fn deploy_tab_context_menu(
1304    //         &mut self,
1305    //         position: Vector2F,
1306    //         target_item_id: usize,
1307    //         cx: &mut ViewContext<Self>,
1308    //     ) {
1309    //         let active_item_id = self.items[self.active_item_index].id();
1310    //         let is_active_item = target_item_id == active_item_id;
1311    //         let target_pane = cx.weak_handle();
1312
1313    //         // 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
1314
1315    //         self.tab_context_menu.update(cx, |menu, cx| {
1316    //             menu.show(
1317    //                 position,
1318    //                 AnchorCorner::TopLeft,
1319    //                 if is_active_item {
1320    //                     vec![
1321    //                         ContextMenuItem::action(
1322    //                             "Close Active Item",
1323    //                             CloseActiveItem { save_intent: None },
1324    //                         ),
1325    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
1326    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
1327    //                         ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
1328    //                         ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
1329    //                         ContextMenuItem::action(
1330    //                             "Close All Items",
1331    //                             CloseAllItems { save_intent: None },
1332    //                         ),
1333    //                     ]
1334    //                 } else {
1335    //                     // 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.
1336    //                     vec![
1337    //                         ContextMenuItem::handler("Close Inactive Item", {
1338    //                             let pane = target_pane.clone();
1339    //                             move |cx| {
1340    //                                 if let Some(pane) = pane.upgrade(cx) {
1341    //                                     pane.update(cx, |pane, cx| {
1342    //                                         pane.close_item_by_id(
1343    //                                             target_item_id,
1344    //                                             SaveIntent::Close,
1345    //                                             cx,
1346    //                                         )
1347    //                                         .detach_and_log_err(cx);
1348    //                                     })
1349    //                                 }
1350    //                             }
1351    //                         }),
1352    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
1353    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
1354    //                         ContextMenuItem::handler("Close Items To The Left", {
1355    //                             let pane = target_pane.clone();
1356    //                             move |cx| {
1357    //                                 if let Some(pane) = pane.upgrade(cx) {
1358    //                                     pane.update(cx, |pane, cx| {
1359    //                                         pane.close_items_to_the_left_by_id(target_item_id, cx)
1360    //                                             .detach_and_log_err(cx);
1361    //                                     })
1362    //                                 }
1363    //                             }
1364    //                         }),
1365    //                         ContextMenuItem::handler("Close Items To The Right", {
1366    //                             let pane = target_pane.clone();
1367    //                             move |cx| {
1368    //                                 if let Some(pane) = pane.upgrade(cx) {
1369    //                                     pane.update(cx, |pane, cx| {
1370    //                                         pane.close_items_to_the_right_by_id(target_item_id, cx)
1371    //                                             .detach_and_log_err(cx);
1372    //                                     })
1373    //                                 }
1374    //                             }
1375    //                         }),
1376    //                         ContextMenuItem::action(
1377    //                             "Close All Items",
1378    //                             CloseAllItems { save_intent: None },
1379    //                         ),
1380    //                     ]
1381    //                 },
1382    //                 cx,
1383    //             );
1384    //         });
1385    //     }
1386
1387    pub fn toolbar(&self) -> &View<Toolbar> {
1388        &self.toolbar
1389    }
1390
1391    pub fn handle_deleted_project_item(
1392        &mut self,
1393        entry_id: ProjectEntryId,
1394        cx: &mut ViewContext<Pane>,
1395    ) -> Option<()> {
1396        let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
1397            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
1398                Some((i, item.item_id()))
1399            } else {
1400                None
1401            }
1402        })?;
1403
1404        self.remove_item(item_index_to_delete, false, cx);
1405        self.nav_history.remove_item(item_id);
1406
1407        Some(())
1408    }
1409
1410    fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
1411        let active_item = self
1412            .items
1413            .get(self.active_item_index)
1414            .map(|item| item.as_ref());
1415        self.toolbar.update(cx, |toolbar, cx| {
1416            toolbar.set_active_item(active_item, cx);
1417        });
1418    }
1419
1420    fn render_tab(
1421        &self,
1422        ix: usize,
1423        item: &Box<dyn ItemHandle>,
1424        detail: usize,
1425        cx: &mut ViewContext<'_, Pane>,
1426    ) -> impl IntoElement {
1427        let label = item.tab_content(Some(detail), cx);
1428        let close_side = &ItemSettings::get_global(cx).close_position;
1429
1430        let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
1431            false => (
1432                cx.theme().colors().text_muted,
1433                cx.theme().colors().tab_inactive_background,
1434                cx.theme().colors().ghost_element_hover,
1435                cx.theme().colors().ghost_element_active,
1436            ),
1437            true => (
1438                cx.theme().colors().text,
1439                cx.theme().colors().tab_active_background,
1440                cx.theme().colors().element_hover,
1441                cx.theme().colors().element_active,
1442            ),
1443        };
1444
1445        let is_active = ix == self.active_item_index;
1446
1447        let indicator = maybe!({
1448            let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
1449                (true, _) => Color::Warning,
1450                (_, true) => Color::Accent,
1451                (false, false) => return None,
1452            };
1453
1454            Some(Indicator::dot().color(indicator_color))
1455        });
1456
1457        let id = item.item_id();
1458
1459        let is_first_item = ix == 0;
1460        let is_last_item = ix == self.items.len() - 1;
1461        let position_relative_to_active_item = ix.cmp(&self.active_item_index);
1462
1463        let tab =
1464            Tab::new(ix)
1465                .position(if is_first_item {
1466                    TabPosition::First
1467                } else if is_last_item {
1468                    TabPosition::Last
1469                } else {
1470                    TabPosition::Middle(position_relative_to_active_item)
1471                })
1472                .close_side(match close_side {
1473                    ClosePosition::Left => ui::TabCloseSide::Start,
1474                    ClosePosition::Right => ui::TabCloseSide::End,
1475                })
1476                .selected(ix == self.active_item_index())
1477                .on_click(cx.listener(move |pane: &mut Self, event, cx| {
1478                    pane.activate_item(ix, true, true, cx)
1479                }))
1480                // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
1481                // .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
1482                // .on_drop(|_view, state: View<DraggedTab>, cx| {
1483                //     eprintln!("{:?}", state.read(cx));
1484                // })
1485                .when_some(item.tab_tooltip_text(cx), |tab, text| {
1486                    tab.tooltip(move |cx| Tooltip::text(text.clone(), cx))
1487                })
1488                .start_slot::<Indicator>(indicator)
1489                .end_slot(
1490                    IconButton::new("close tab", Icon::Close)
1491                        .icon_color(Color::Muted)
1492                        .size(ButtonSize::None)
1493                        .icon_size(IconSize::XSmall)
1494                        .on_click(cx.listener(move |pane, _, cx| {
1495                            pane.close_item_by_id(id, SaveIntent::Close, cx)
1496                                .detach_and_log_err(cx);
1497                        })),
1498                )
1499                .child(label);
1500
1501        right_click_menu(ix).trigger(tab).menu(|cx| {
1502            ContextMenu::build(cx, |menu, cx| {
1503                menu.action("Close", CloseActiveItem { save_intent: None }.boxed_clone())
1504                    .action("Close Others", CloseInactiveItems.boxed_clone())
1505                    .separator()
1506                    .action("Close Left", CloseItemsToTheLeft.boxed_clone())
1507                    .action("Close Right", CloseItemsToTheRight.boxed_clone())
1508                    .separator()
1509                    .action("Close Clean", CloseCleanItems.boxed_clone())
1510                    .action(
1511                        "Close All",
1512                        CloseAllItems { save_intent: None }.boxed_clone(),
1513                    )
1514            })
1515        })
1516    }
1517
1518    fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement {
1519        div()
1520            .id("tab_bar")
1521            .group("tab_bar")
1522            .track_focus(&self.tab_bar_focus_handle)
1523            .w_full()
1524            // 30px @ 16px/rem
1525            .h(rems(1.875))
1526            .overflow_hidden()
1527            .flex()
1528            .flex_none()
1529            .bg(cx.theme().colors().tab_bar_background)
1530            // Left Side
1531            .child(
1532                h_stack()
1533                    .flex()
1534                    .flex_none()
1535                    .gap_1()
1536                    .px_1()
1537                    .border_b()
1538                    .border_r()
1539                    .border_color(cx.theme().colors().border)
1540                    // Nav Buttons
1541                    .child(
1542                        IconButton::new("navigate_backward", Icon::ArrowLeft)
1543                            .icon_size(IconSize::Small)
1544                            .on_click({
1545                                let view = cx.view().clone();
1546                                move |_, cx| view.update(cx, Self::navigate_backward)
1547                            })
1548                            .disabled(!self.can_navigate_backward()),
1549                    )
1550                    .child(
1551                        IconButton::new("navigate_forward", Icon::ArrowRight)
1552                            .icon_size(IconSize::Small)
1553                            .on_click({
1554                                let view = cx.view().clone();
1555                                move |_, cx| view.update(cx, Self::navigate_backward)
1556                            })
1557                            .disabled(!self.can_navigate_forward()),
1558                    ),
1559            )
1560            .child(
1561                div()
1562                    .relative()
1563                    .flex_1()
1564                    .h_full()
1565                    .overflow_hidden_x()
1566                    .child(
1567                        div()
1568                            .absolute()
1569                            .top_0()
1570                            .left_0()
1571                            .z_index(1)
1572                            .size_full()
1573                            .border_b()
1574                            .border_color(cx.theme().colors().border),
1575                    )
1576                    .child(
1577                        h_stack().id("tabs").z_index(2).children(
1578                            self.items
1579                                .iter()
1580                                .enumerate()
1581                                .zip(self.tab_details(cx))
1582                                .map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
1583                        ),
1584                    ),
1585            )
1586            // Right Side
1587            .child(
1588                h_stack()
1589                    .flex()
1590                    .flex_none()
1591                    .gap_1()
1592                    .px_1()
1593                    .border_b()
1594                    .border_l()
1595                    .border_color(cx.theme().colors().border)
1596                    .child(
1597                        div()
1598                            .flex()
1599                            .items_center()
1600                            .gap_px()
1601                            .child(
1602                                IconButton::new("plus", Icon::Plus)
1603                                    .icon_size(IconSize::Small)
1604                                    .on_click(cx.listener(|this, _, cx| {
1605                                        let menu = ContextMenu::build(cx, |menu, cx| {
1606                                            menu.action("New File", NewFile.boxed_clone())
1607                                                .action(
1608                                                    "New Terminal",
1609                                                    NewCenterTerminal.boxed_clone(),
1610                                                )
1611                                                .action("New Search", NewSearch.boxed_clone())
1612                                        });
1613                                        cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
1614                                            this.focus(cx);
1615                                            this.new_item_menu = None;
1616                                        })
1617                                        .detach();
1618                                        this.new_item_menu = Some(menu);
1619                                    })),
1620                            )
1621                            .when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
1622                                el.child(Self::render_menu_overlay(new_item_menu))
1623                            })
1624                            .child(
1625                                IconButton::new("split", Icon::Split)
1626                                    .icon_size(IconSize::Small)
1627                                    .on_click(cx.listener(|this, _, cx| {
1628                                        let menu = ContextMenu::build(cx, |menu, cx| {
1629                                            menu.action("Split Right", SplitRight.boxed_clone())
1630                                                .action("Split Left", SplitLeft.boxed_clone())
1631                                                .action("Split Up", SplitUp.boxed_clone())
1632                                                .action("Split Down", SplitDown.boxed_clone())
1633                                        });
1634                                        cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
1635                                            this.focus(cx);
1636                                            this.split_item_menu = None;
1637                                        })
1638                                        .detach();
1639                                        this.split_item_menu = Some(menu);
1640                                    })),
1641                            )
1642                            .when_some(self.split_item_menu.as_ref(), |el, split_item_menu| {
1643                                el.child(Self::render_menu_overlay(split_item_menu))
1644                            }),
1645                    ),
1646            )
1647    }
1648
1649    fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
1650        div()
1651            .absolute()
1652            .z_index(1)
1653            .bottom_0()
1654            .right_0()
1655            .size_0()
1656            .child(overlay().anchor(AnchorCorner::TopRight).child(menu.clone()))
1657    }
1658
1659    //     fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
1660    //         let theme = theme::current(cx).clone();
1661
1662    //         let pane = cx.handle().downgrade();
1663    //         let autoscroll = if mem::take(&mut self.autoscroll) {
1664    //             Some(self.active_item_index)
1665    //         } else {
1666    //             None
1667    //         };
1668
1669    //         let pane_active = self.has_focus;
1670
1671    //         enum Tabs {}
1672    //         let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
1673    //         for (ix, (item, detail)) in self
1674    //             .items
1675    //             .iter()
1676    //             .cloned()
1677    //             .zip(self.tab_details(cx))
1678    //             .enumerate()
1679    //         {
1680    //             let git_status = item
1681    //                 .project_path(cx)
1682    //                 .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
1683    //                 .and_then(|entry| entry.git_status());
1684
1685    //             let detail = if detail == 0 { None } else { Some(detail) };
1686    //             let tab_active = ix == self.active_item_index;
1687
1688    //             row.add_child({
1689    //                 enum TabDragReceiver {}
1690    //                 let mut receiver =
1691    //                     dragged_item_receiver::<TabDragReceiver, _, _>(self, ix, ix, true, None, cx, {
1692    //                         let item = item.clone();
1693    //                         let pane = pane.clone();
1694    //                         let detail = detail.clone();
1695
1696    //                         let theme = theme::current(cx).clone();
1697    //                         let mut tooltip_theme = theme.tooltip.clone();
1698    //                         tooltip_theme.max_text_width = None;
1699    //                         let tab_tooltip_text =
1700    //                             item.tab_tooltip_text(cx).map(|text| text.into_owned());
1701
1702    //                         let mut tab_style = theme
1703    //                             .workspace
1704    //                             .tab_bar
1705    //                             .tab_style(pane_active, tab_active)
1706    //                             .clone();
1707    //                         let should_show_status = settings::get::<ItemSettings>(cx).git_status;
1708    //                         if should_show_status && git_status != None {
1709    //                             tab_style.label.text.color = match git_status.unwrap() {
1710    //                                 GitFileStatus::Added => tab_style.git.inserted,
1711    //                                 GitFileStatus::Modified => tab_style.git.modified,
1712    //                                 GitFileStatus::Conflict => tab_style.git.conflict,
1713    //                             };
1714    //                         }
1715
1716    //                         move |mouse_state, cx| {
1717    //                             let hovered = mouse_state.hovered();
1718
1719    //                             enum Tab {}
1720    //                             let mouse_event_handler =
1721    //                                 MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
1722    //                                     Self::render_tab(
1723    //                                         &item,
1724    //                                         pane.clone(),
1725    //                                         ix == 0,
1726    //                                         detail,
1727    //                                         hovered,
1728    //                                         &tab_style,
1729    //                                         cx,
1730    //                                     )
1731    //                                 })
1732    //                                 .on_down(MouseButton::Left, move |_, this, cx| {
1733    //                                     this.activate_item(ix, true, true, cx);
1734    //                                 })
1735    //                                 .on_click(MouseButton::Middle, {
1736    //                                     let item_id = item.id();
1737    //                                     move |_, pane, cx| {
1738    //                                         pane.close_item_by_id(item_id, SaveIntent::Close, cx)
1739    //                                             .detach_and_log_err(cx);
1740    //                                     }
1741    //                                 })
1742    //                                 .on_down(
1743    //                                     MouseButton::Right,
1744    //                                     move |event, pane, cx| {
1745    //                                         pane.deploy_tab_context_menu(event.position, item.id(), cx);
1746    //                                     },
1747    //                                 );
1748
1749    //                             if let Some(tab_tooltip_text) = tab_tooltip_text {
1750    //                                 mouse_event_handler
1751    //                                     .with_tooltip::<Self>(
1752    //                                         ix,
1753    //                                         tab_tooltip_text,
1754    //                                         None,
1755    //                                         tooltip_theme,
1756    //                                         cx,
1757    //                                     )
1758    //                                     .into_any()
1759    //                             } else {
1760    //                                 mouse_event_handler.into_any()
1761    //                             }
1762    //                         }
1763    //                     });
1764
1765    //                 if !pane_active || !tab_active {
1766    //                     receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
1767    //                 }
1768
1769    //                 receiver.as_draggable(
1770    //                     DraggedItem {
1771    //                         handle: item,
1772    //                         pane: pane.clone(),
1773    //                     },
1774    //                     {
1775    //                         let theme = theme::current(cx).clone();
1776
1777    //                         let detail = detail.clone();
1778    //                         move |_, dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
1779    //                             let tab_style = &theme.workspace.tab_bar.dragged_tab;
1780    //                             Self::render_dragged_tab(
1781    //                                 &dragged_item.handle,
1782    //                                 dragged_item.pane.clone(),
1783    //                                 false,
1784    //                                 detail,
1785    //                                 false,
1786    //                                 &tab_style,
1787    //                                 cx,
1788    //                             )
1789    //                         }
1790    //                     },
1791    //                 )
1792    //             })
1793    //         }
1794
1795    //         // Use the inactive tab style along with the current pane's active status to decide how to render
1796    //         // the filler
1797    //         let filler_index = self.items.len();
1798    //         let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
1799    //         enum Filler {}
1800    //         row.add_child(
1801    //             dragged_item_receiver::<Filler, _, _>(self, 0, filler_index, true, None, cx, |_, _| {
1802    //                 Empty::new()
1803    //                     .contained()
1804    //                     .with_style(filler_style.container)
1805    //                     .with_border(filler_style.container.border)
1806    //             })
1807    //             .flex(1., true)
1808    //             .into_any_named("filler"),
1809    //         );
1810
1811    //         row
1812    //     }
1813
1814    fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
1815        let mut tab_details = self.items.iter().map(|_| 0).collect::<Vec<_>>();
1816
1817        let mut tab_descriptions = HashMap::default();
1818        let mut done = false;
1819        while !done {
1820            done = true;
1821
1822            // Store item indices by their tab description.
1823            for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
1824                if let Some(description) = item.tab_description(*detail, cx) {
1825                    if *detail == 0
1826                        || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
1827                    {
1828                        tab_descriptions
1829                            .entry(description)
1830                            .or_insert(Vec::new())
1831                            .push(ix);
1832                    }
1833                }
1834            }
1835
1836            // If two or more items have the same tab description, increase eir level
1837            // of detail and try again.
1838            for (_, item_ixs) in tab_descriptions.drain() {
1839                if item_ixs.len() > 1 {
1840                    done = false;
1841                    for ix in item_ixs {
1842                        tab_details[ix] += 1;
1843                    }
1844                }
1845            }
1846        }
1847
1848        tab_details
1849    }
1850
1851    //     fn render_tab(
1852    //         item: &Box<dyn ItemHandle>,
1853    //         pane: WeakView<Pane>,
1854    //         first: bool,
1855    //         detail: Option<usize>,
1856    //         hovered: bool,
1857    //         tab_style: &theme::Tab,
1858    //         cx: &mut ViewContext<Self>,
1859    //     ) -> AnyElement<Self> {
1860    //         let title = item.tab_content(detail, &tab_style, cx);
1861    //         Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
1862    //     }
1863
1864    //     fn render_dragged_tab(
1865    //         item: &Box<dyn ItemHandle>,
1866    //         pane: WeakView<Pane>,
1867    //         first: bool,
1868    //         detail: Option<usize>,
1869    //         hovered: bool,
1870    //         tab_style: &theme::Tab,
1871    //         cx: &mut ViewContext<Workspace>,
1872    //     ) -> AnyElement<Workspace> {
1873    //         let title = item.dragged_tab_content(detail, &tab_style, cx);
1874    //         Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
1875    //     }
1876
1877    //     fn render_tab_with_title<T: View>(
1878    //         title: AnyElement<T>,
1879    //         item: &Box<dyn ItemHandle>,
1880    //         pane: WeakView<Pane>,
1881    //         first: bool,
1882    //         hovered: bool,
1883    //         tab_style: &theme::Tab,
1884    //         cx: &mut ViewContext<T>,
1885    //     ) -> AnyElement<T> {
1886    //         let mut container = tab_style.container.clone();
1887    //         if first {
1888    //             container.border.left = false;
1889    //         }
1890
1891    //         let buffer_jewel_element = {
1892    //             let diameter = 7.0;
1893    //             let icon_color = if item.has_conflict(cx) {
1894    //                 Some(tab_style.icon_conflict)
1895    //             } else if item.is_dirty(cx) {
1896    //                 Some(tab_style.icon_dirty)
1897    //             } else {
1898    //                 None
1899    //             };
1900
1901    //             Canvas::new(move |bounds, _, _, cx| {
1902    //                 if let Some(color) = icon_color {
1903    //                     let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
1904    //                     cx.scene().push_quad(Quad {
1905    //                         bounds: square,
1906    //                         background: Some(color),
1907    //                         border: Default::default(),
1908    //                         corner_radii: (diameter / 2.).into(),
1909    //                     });
1910    //                 }
1911    //             })
1912    //             .constrained()
1913    //             .with_width(diameter)
1914    //             .with_height(diameter)
1915    //             .aligned()
1916    //         };
1917
1918    //         let title_element = title.aligned().contained().with_style(ContainerStyle {
1919    //             margin: Margin {
1920    //                 left: tab_style.spacing,
1921    //                 right: tab_style.spacing,
1922    //                 ..Default::default()
1923    //             },
1924    //             ..Default::default()
1925    //         });
1926
1927    //         let close_element = if hovered {
1928    //             let item_id = item.id();
1929    //             enum TabCloseButton {}
1930    //             let icon = Svg::new("icons/x.svg");
1931    //             MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
1932    //                 if mouse_state.hovered() {
1933    //                     icon.with_color(tab_style.icon_close_active)
1934    //                 } else {
1935    //                     icon.with_color(tab_style.icon_close)
1936    //                 }
1937    //             })
1938    //             .with_padding(Padding::uniform(4.))
1939    //             .with_cursor_style(CursorStyle::PointingHand)
1940    //             .on_click(MouseButton::Left, {
1941    //                 let pane = pane.clone();
1942    //                 move |_, _, cx| {
1943    //                     let pane = pane.clone();
1944    //                     cx.window_context().defer(move |cx| {
1945    //                         if let Some(pane) = pane.upgrade(cx) {
1946    //                             pane.update(cx, |pane, cx| {
1947    //                                 pane.close_item_by_id(item_id, SaveIntent::Close, cx)
1948    //                                     .detach_and_log_err(cx);
1949    //                             });
1950    //                         }
1951    //                     });
1952    //                 }
1953    //             })
1954    //             .into_any_named("close-tab-icon")
1955    //             .constrained()
1956    //         } else {
1957    //             Empty::new().constrained()
1958    //         }
1959    //         .with_width(tab_style.close_icon_width)
1960    //         .aligned();
1961
1962    //         let close_right = settings::get::<ItemSettings>(cx).close_position.right();
1963
1964    //         if close_right {
1965    //             Flex::row()
1966    //                 .with_child(buffer_jewel_element)
1967    //                 .with_child(title_element)
1968    //                 .with_child(close_element)
1969    //         } else {
1970    //             Flex::row()
1971    //                 .with_child(close_element)
1972    //                 .with_child(title_element)
1973    //                 .with_child(buffer_jewel_element)
1974    //         }
1975    //         .contained()
1976    //         .with_style(container)
1977    //         .constrained()
1978    //         .with_height(tab_style.height)
1979    //         .into_any()
1980    //     }
1981
1982    //     pub fn render_tab_bar_button<
1983    //         F1: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
1984    //         F2: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
1985    //     >(
1986    //         index: usize,
1987    //         icon: &'static str,
1988    //         is_active: bool,
1989    //         tooltip: Option<(&'static str, Option<Box<dyn Action>>)>,
1990    //         cx: &mut ViewContext<Pane>,
1991    //         on_click: F1,
1992    //         on_down: F2,
1993    //         context_menu: Option<ViewHandle<ContextMenu>>,
1994    //     ) -> AnyElement<Pane> {
1995    //         enum TabBarButton {}
1996
1997    //         let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
1998    //             let theme = &settings2::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
1999    //             let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
2000    //             Svg::new(icon)
2001    //                 .with_color(style.color)
2002    //                 .constrained()
2003    //                 .with_width(style.icon_width)
2004    //                 .aligned()
2005    //                 .constrained()
2006    //                 .with_width(style.button_width)
2007    //                 .with_height(style.button_width)
2008    //         })
2009    //         .with_cursor_style(CursorStyle::PointingHand)
2010    //         .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx))
2011    //         .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
2012    //         .into_any();
2013    //         if let Some((tooltip, action)) = tooltip {
2014    //             let tooltip_style = settings::get::<ThemeSettings>(cx).theme.tooltip.clone();
2015    //             button = button
2016    //                 .with_tooltip::<TabBarButton>(index, tooltip, action, tooltip_style, cx)
2017    //                 .into_any();
2018    //         }
2019
2020    //         Stack::new()
2021    //             .with_child(button)
2022    //             .with_children(
2023    //                 context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
2024    //             )
2025    //             .flex(1., false)
2026    //             .into_any_named("tab bar button")
2027    //     }
2028
2029    //     fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2030    //         let background = theme.workspace.background;
2031    //         Empty::new()
2032    //             .contained()
2033    //             .with_background_color(background)
2034    //             .into_any()
2035    //     }
2036
2037    pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
2038        self.zoomed = zoomed;
2039        cx.notify();
2040    }
2041
2042    pub fn is_zoomed(&self) -> bool {
2043        self.zoomed
2044    }
2045}
2046
2047impl FocusableView for Pane {
2048    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
2049        self.focus_handle.clone()
2050    }
2051}
2052
2053impl Render for Pane {
2054    type Element = Focusable<Div>;
2055
2056    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
2057        let this = cx.view().downgrade();
2058
2059        v_stack()
2060            .key_context("Pane")
2061            .track_focus(&self.focus_handle)
2062            .size_full()
2063            .overflow_hidden()
2064            .on_focus_in({
2065                let this = this.clone();
2066                move |event, cx| {
2067                    this.update(cx, |this, cx| this.focus_in(cx)).ok();
2068                }
2069            })
2070            .on_focus_out({
2071                let this = this.clone();
2072                move |event, cx| {
2073                    this.update(cx, |this, cx| this.focus_out(cx)).ok();
2074                }
2075            })
2076            .on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
2077            .on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
2078            .on_action(
2079                cx.listener(|pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)),
2080            )
2081            .on_action(cx.listener(|pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)))
2082            .on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx)))
2083            .on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx)))
2084            .on_action(cx.listener(Pane::toggle_zoom))
2085            .on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
2086                pane.activate_item(action.0, true, true, cx);
2087            }))
2088            .on_action(cx.listener(|pane: &mut Pane, _: &ActivateLastItem, cx| {
2089                pane.activate_item(pane.items.len() - 1, true, true, cx);
2090            }))
2091            .on_action(cx.listener(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
2092                pane.activate_prev_item(true, cx);
2093            }))
2094            .on_action(cx.listener(|pane: &mut Pane, _: &ActivateNextItem, cx| {
2095                pane.activate_next_item(true, cx);
2096            }))
2097            .on_action(
2098                cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
2099                    pane.close_active_item(action, cx)
2100                        .map(|task| task.detach_and_log_err(cx));
2101                }),
2102            )
2103            .on_action(
2104                cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| {
2105                    pane.close_inactive_items(action, cx)
2106                        .map(|task| task.detach_and_log_err(cx));
2107                }),
2108            )
2109            .on_action(
2110                cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| {
2111                    pane.close_clean_items(action, cx)
2112                        .map(|task| task.detach_and_log_err(cx));
2113                }),
2114            )
2115            .on_action(
2116                cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| {
2117                    pane.close_items_to_the_left(action, cx)
2118                        .map(|task| task.detach_and_log_err(cx));
2119                }),
2120            )
2121            .on_action(
2122                cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| {
2123                    pane.close_items_to_the_right(action, cx)
2124                        .map(|task| task.detach_and_log_err(cx));
2125                }),
2126            )
2127            .on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| {
2128                pane.close_all_items(action, cx)
2129                    .map(|task| task.detach_and_log_err(cx));
2130            }))
2131            .on_action(
2132                cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
2133                    pane.close_active_item(action, cx)
2134                        .map(|task| task.detach_and_log_err(cx));
2135                }),
2136            )
2137            .child(self.render_tab_bar(cx))
2138            .child(self.toolbar.clone())
2139            .child(if let Some(item) = self.active_item() {
2140                div().flex().flex_1().child(item.to_any())
2141            } else {
2142                h_stack()
2143                    .items_center()
2144                    .size_full()
2145                    .justify_center()
2146                    .child(Label::new("Open a file or project to get started.").color(Color::Muted))
2147            })
2148            // enum MouseNavigationHandler {}
2149            // MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
2150            //     let active_item_index = self.active_item_index;
2151            //     if let Some(active_item) = self.active_item() {
2152            //         Flex::column()
2153            //             .with_child({
2154            //                 let theme = theme::current(cx).clone();
2155            //                 let mut stack = Stack::new();
2156            //                 enum TabBarEventHandler {}
2157            //                 stack.add_child(
2158            //                     MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
2159            //                         Empty::new()
2160            //                             .contained()
2161            //                             .with_style(theme.workspace.tab_bar.container)
2162            //                     })
2163            //                     .on_down(
2164            //                         MouseButton::Left,
2165            //                         move |_, this, cx| {
2166            //                             this.activate_item(active_item_index, true, true, cx);
2167            //                         },
2168            //                     ),
2169            //                 );
2170            //                 let tooltip_style = theme.tooltip.clone();
2171            //                 let tab_bar_theme = theme.workspace.tab_bar.clone();
2172            //                 let nav_button_height = tab_bar_theme.height;
2173            //                 let button_style = tab_bar_theme.nav_button;
2174            //                 let border_for_nav_buttons = tab_bar_theme
2175            //                     .tab_style(false, false)
2176            //                     .container
2177            //                     .border
2178            //                     .clone();
2179            //                 let mut tab_row = Flex::row()
2180            //                     .with_child(nav_button(
2181            //                         "icons/arrow_left.svg",
2182            //                         button_style.clone(),
2183            //                         nav_button_height,
2184            //                         tooltip_style.clone(),
2185            //                         self.can_navigate_backward(),
2186            //                         {
2187            //                             move |pane, cx| {
2188            //                                 if let Some(workspace) = pane.workspace.upgrade(cx) {
2189            //                                     let pane = cx.weak_handle();
2190            //                                     cx.window_context().defer(move |cx| {
2191            //                                         workspace.update(cx, |workspace, cx| {
2192            //                                             workspace
2193            //                                                 .go_back(pane, cx)
2194            //                                                 .detach_and_log_err(cx)
2195            //                                         })
2196            //                                     })
2197            //                                 }
2198            //                             }
2199            //                         },
2200            //                         super::GoBack,
2201            //                         "Go Back",
2202            //                         cx,
2203            //                     ))
2204            //                     .with_child(
2205            //                         nav_button(
2206            //                             "icons/arrow_right.svg",
2207            //                             button_style.clone(),
2208            //                             nav_button_height,
2209            //                             tooltip_style,
2210            //                             self.can_navigate_forward(),
2211            //                             {
2212            //                                 move |pane, cx| {
2213            //                                     if let Some(workspace) = pane.workspace.upgrade(cx) {
2214            //                                         let pane = cx.weak_handle();
2215            //                                         cx.window_context().defer(move |cx| {
2216            //                                             workspace.update(cx, |workspace, cx| {
2217            //                                                 workspace
2218            //                                                     .go_forward(pane, cx)
2219            //                                                     .detach_and_log_err(cx)
2220            //                                             })
2221            //                                         })
2222            //                                     }
2223            //                                 }
2224            //                             },
2225            //                             super::GoForward,
2226            //                             "Go Forward",
2227            //                             cx,
2228            //                         )
2229            //                         .contained()
2230            //                         .with_border(border_for_nav_buttons),
2231            //                     )
2232            //                     .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
2233            //                 if self.has_focus {
2234            //                     let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
2235            //                     tab_row.add_child(
2236            //                         (render_tab_bar_buttons)(self, cx)
2237            //                             .contained()
2238            //                             .with_style(theme.workspace.tab_bar.pane_button_container)
2239            //                             .flex(1., false)
2240            //                             .into_any(),
2241            //                     )
2242            //                 }
2243            //                 stack.add_child(tab_row);
2244            //                 stack
2245            //                     .constrained()
2246            //                     .with_height(theme.workspace.tab_bar.height)
2247            //                     .flex(1., false)
2248            //                     .into_any_named("tab bar")
2249            //             })
2250            //             .with_child({
2251            //                 enum PaneContentTabDropTarget {}
2252            //                 dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
2253            //                     self,
2254            //                     0,
2255            //                     self.active_item_index + 1,
2256            //                     !self.can_split,
2257            //                     if self.can_split { Some(100.) } else { None },
2258            //                     cx,
2259            //                     {
2260            //                         let toolbar = self.toolbar.clone();
2261            //                         let toolbar_hidden = toolbar.read(cx).hidden();
2262            //                         move |_, cx| {
2263            //                             Flex::column()
2264            //                                 .with_children(
2265            //                                     (!toolbar_hidden)
2266            //                                         .then(|| ChildView::new(&toolbar, cx).expanded()),
2267            //                                 )
2268            //                                 .with_child(
2269            //                                     ChildView::new(active_item.as_any(), cx).flex(1., true),
2270            //                                 )
2271            //                         }
2272            //                     },
2273            //                 )
2274            //                 .flex(1., true)
2275            //             })
2276            //             .with_child(ChildView::new(&self.tab_context_menu, cx))
2277            //             .into_any()
2278            //     } else {
2279            //         enum EmptyPane {}
2280            //         let theme = theme::current(cx).clone();
2281            //         dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
2282            //             self.render_blank_pane(&theme, cx)
2283            //         })
2284            //         .on_down(MouseButton::Left, |_, _, cx| {
2285            //             cx.focus_parent();
2286            //         })
2287            //         .into_any()
2288            //     }
2289            // })
2290            .on_mouse_down(
2291                MouseButton::Navigate(NavigationDirection::Back),
2292                cx.listener(|pane, _, cx| {
2293                    if let Some(workspace) = pane.workspace.upgrade() {
2294                        let pane = cx.view().downgrade();
2295                        cx.window_context().defer(move |cx| {
2296                            workspace.update(cx, |workspace, cx| {
2297                                workspace.go_back(pane, cx).detach_and_log_err(cx)
2298                            })
2299                        })
2300                    }
2301                }),
2302            )
2303            .on_mouse_down(
2304                MouseButton::Navigate(NavigationDirection::Forward),
2305                cx.listener(|pane, _, cx| {
2306                    if let Some(workspace) = pane.workspace.upgrade() {
2307                        let pane = cx.view().downgrade();
2308                        cx.window_context().defer(move |cx| {
2309                            workspace.update(cx, |workspace, cx| {
2310                                workspace.go_forward(pane, cx).detach_and_log_err(cx)
2311                            })
2312                        })
2313                    }
2314                }),
2315            )
2316        // .into_any_named("pane")
2317    }
2318
2319    // fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
2320    //     if !self.has_focus {
2321    //         self.has_focus = true;
2322    //         cx.emit(Event::Focus);
2323    //         cx.notify();
2324    //     }
2325
2326    //     self.toolbar.update(cx, |toolbar, cx| {
2327    //         toolbar.focus_changed(true, cx);
2328    //     });
2329
2330    //     if let Some(active_item) = self.active_item() {
2331    //         if cx.is_self_focused() {
2332    //             // Pane was focused directly. We need to either focus a view inside the active item,
2333    //             // or focus the active item itself
2334    //             if let Some(weak_last_focused_view) =
2335    //                 self.last_focused_view_by_item.get(&active_item.id())
2336    //             {
2337    //                 if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
2338    //                     cx.focus(&last_focused_view);
2339    //                     return;
2340    //                 } else {
2341    //                     self.last_focused_view_by_item.remove(&active_item.id());
2342    //                 }
2343    //             }
2344
2345    //             cx.focus(active_item.as_any());
2346    //         } else if focused != self.tab_bar_context_menu.handle {
2347    //             self.last_focused_view_by_item
2348    //                 .insert(active_item.id(), focused.downgrade());
2349    //         }
2350    //     }
2351    // }
2352
2353    // fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
2354    //     self.has_focus = false;
2355    //     self.toolbar.update(cx, |toolbar, cx| {
2356    //         toolbar.focus_changed(false, cx);
2357    //     });
2358    //     cx.notify();
2359    // }
2360
2361    // fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
2362    //     Self::reset_to_default_keymap_context(keymap);
2363    // }
2364}
2365
2366impl ItemNavHistory {
2367    pub fn push<D: 'static + Send + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
2368        self.history.push(data, self.item.clone(), cx);
2369    }
2370
2371    pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
2372        self.history.pop(NavigationMode::GoingBack, cx)
2373    }
2374
2375    pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
2376        self.history.pop(NavigationMode::GoingForward, cx)
2377    }
2378}
2379
2380impl NavHistory {
2381    pub fn for_each_entry(
2382        &self,
2383        cx: &AppContext,
2384        mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
2385    ) {
2386        let borrowed_history = self.0.lock();
2387        borrowed_history
2388            .forward_stack
2389            .iter()
2390            .chain(borrowed_history.backward_stack.iter())
2391            .chain(borrowed_history.closed_stack.iter())
2392            .for_each(|entry| {
2393                if let Some(project_and_abs_path) =
2394                    borrowed_history.paths_by_item.get(&entry.item.id())
2395                {
2396                    f(entry, project_and_abs_path.clone());
2397                } else if let Some(item) = entry.item.upgrade() {
2398                    if let Some(path) = item.project_path(cx) {
2399                        f(entry, (path, None));
2400                    }
2401                }
2402            })
2403    }
2404
2405    pub fn set_mode(&mut self, mode: NavigationMode) {
2406        self.0.lock().mode = mode;
2407    }
2408
2409    pub fn mode(&self) -> NavigationMode {
2410        self.0.lock().mode
2411    }
2412
2413    pub fn disable(&mut self) {
2414        self.0.lock().mode = NavigationMode::Disabled;
2415    }
2416
2417    pub fn enable(&mut self) {
2418        self.0.lock().mode = NavigationMode::Normal;
2419    }
2420
2421    pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
2422        let mut state = self.0.lock();
2423        let entry = match mode {
2424            NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
2425                return None
2426            }
2427            NavigationMode::GoingBack => &mut state.backward_stack,
2428            NavigationMode::GoingForward => &mut state.forward_stack,
2429            NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
2430        }
2431        .pop_back();
2432        if entry.is_some() {
2433            state.did_update(cx);
2434        }
2435        entry
2436    }
2437
2438    pub fn push<D: 'static + Send + Any>(
2439        &mut self,
2440        data: Option<D>,
2441        item: Arc<dyn WeakItemHandle>,
2442        cx: &mut WindowContext,
2443    ) {
2444        let state = &mut *self.0.lock();
2445        match state.mode {
2446            NavigationMode::Disabled => {}
2447            NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
2448                if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2449                    state.backward_stack.pop_front();
2450                }
2451                state.backward_stack.push_back(NavigationEntry {
2452                    item,
2453                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2454                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2455                });
2456                state.forward_stack.clear();
2457            }
2458            NavigationMode::GoingBack => {
2459                if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2460                    state.forward_stack.pop_front();
2461                }
2462                state.forward_stack.push_back(NavigationEntry {
2463                    item,
2464                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2465                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2466                });
2467            }
2468            NavigationMode::GoingForward => {
2469                if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2470                    state.backward_stack.pop_front();
2471                }
2472                state.backward_stack.push_back(NavigationEntry {
2473                    item,
2474                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2475                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2476                });
2477            }
2478            NavigationMode::ClosingItem => {
2479                if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2480                    state.closed_stack.pop_front();
2481                }
2482                state.closed_stack.push_back(NavigationEntry {
2483                    item,
2484                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2485                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2486                });
2487            }
2488        }
2489        state.did_update(cx);
2490    }
2491
2492    pub fn remove_item(&mut self, item_id: EntityId) {
2493        let mut state = self.0.lock();
2494        state.paths_by_item.remove(&item_id);
2495        state
2496            .backward_stack
2497            .retain(|entry| entry.item.id() != item_id);
2498        state
2499            .forward_stack
2500            .retain(|entry| entry.item.id() != item_id);
2501        state
2502            .closed_stack
2503            .retain(|entry| entry.item.id() != item_id);
2504    }
2505
2506    pub fn path_for_item(&self, item_id: EntityId) -> Option<(ProjectPath, Option<PathBuf>)> {
2507        self.0.lock().paths_by_item.get(&item_id).cloned()
2508    }
2509}
2510
2511impl NavHistoryState {
2512    pub fn did_update(&self, cx: &mut WindowContext) {
2513        if let Some(pane) = self.pane.upgrade() {
2514            cx.defer(move |cx| {
2515                pane.update(cx, |pane, cx| pane.history_updated(cx));
2516            });
2517        }
2518    }
2519}
2520
2521// pub struct PaneBackdrop<V> {
2522//     child_view: usize,
2523//     child: AnyElement<V>,
2524// }
2525
2526// impl<V> PaneBackdrop<V> {
2527//     pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
2528//         PaneBackdrop {
2529//             child,
2530//             child_view: pane_item_view,
2531//         }
2532//     }
2533// }
2534
2535// impl<V: 'static> Element<V> for PaneBackdrop<V> {
2536//     type LayoutState = ();
2537
2538//     type PaintState = ();
2539
2540//     fn layout(
2541//         &mut self,
2542//         constraint: gpui::SizeConstraint,
2543//         view: &mut V,
2544//         cx: &mut ViewContext<V>,
2545//     ) -> (Vector2F, Self::LayoutState) {
2546//         let size = self.child.layout(constraint, view, cx);
2547//         (size, ())
2548//     }
2549
2550//     fn paint(
2551//         &mut self,
2552//         bounds: RectF,
2553//         visible_bounds: RectF,
2554//         _: &mut Self::LayoutState,
2555//         view: &mut V,
2556//         cx: &mut ViewContext<V>,
2557//     ) -> Self::PaintState {
2558//         let background = theme::current(cx).editor.background;
2559
2560//         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
2561
2562//         cx.scene().push_quad(gpui::Quad {
2563//             bounds: RectF::new(bounds.origin(), bounds.size()),
2564//             background: Some(background),
2565//             ..Default::default()
2566//         });
2567
2568//         let child_view_id = self.child_view;
2569//         cx.scene().push_mouse_region(
2570//             MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
2571//                 gpui::platform::MouseButton::Left,
2572//                 move |_, _: &mut V, cx| {
2573//                     let window = cx.window();
2574//                     cx.app_context().focus(window, Some(child_view_id))
2575//                 },
2576//             ),
2577//         );
2578
2579//         cx.scene().push_layer(Some(bounds));
2580//         self.child.paint(bounds.origin(), visible_bounds, view, cx);
2581//         cx.scene().pop_layer();
2582//     }
2583
2584//     fn rect_for_text_range(
2585//         &self,
2586//         range_utf16: std::ops::Range<usize>,
2587//         _bounds: RectF,
2588//         _visible_bounds: RectF,
2589//         _layout: &Self::LayoutState,
2590//         _paint: &Self::PaintState,
2591//         view: &V,
2592//         cx: &gpui::ViewContext<V>,
2593//     ) -> Option<RectF> {
2594//         self.child.rect_for_text_range(range_utf16, view, cx)
2595//     }
2596
2597//     fn debug(
2598//         &self,
2599//         _bounds: RectF,
2600//         _layout: &Self::LayoutState,
2601//         _paint: &Self::PaintState,
2602//         view: &V,
2603//         cx: &gpui::ViewContext<V>,
2604//     ) -> serde_json::Value {
2605//         gpui::json::json!({
2606//             "type": "Pane Back Drop",
2607//             "view": self.child_view,
2608//             "child": self.child.debug(view, cx),
2609//         })
2610//     }
2611// }
2612
2613fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
2614    let path = buffer_path
2615        .as_ref()
2616        .and_then(|p| p.path.to_str())
2617        .unwrap_or(&"This buffer");
2618    let path = truncate_and_remove_front(path, 80);
2619    format!("{path} contains unsaved edits. Do you want to save it?")
2620}
2621
2622// todo!("uncomment tests")
2623// #[cfg(test)]
2624// mod tests {
2625//     use super::*;
2626//     use crate::item::test::{TestItem, TestProjectItem};
2627//     use gpui::TestAppContext;
2628//     use project::FakeFs;
2629//     use settings::SettingsStore;
2630
2631//     #[gpui::test]
2632//     async fn test_remove_active_empty(cx: &mut TestAppContext) {
2633//         init_test(cx);
2634//         let fs = FakeFs::new(cx.background());
2635
2636//         let project = Project::test(fs, None, cx).await;
2637//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2638//         let workspace = window.root(cx);
2639//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2640
2641//         pane.update(cx, |pane, cx| {
2642//             assert!(pane
2643//                 .close_active_item(&CloseActiveItem { save_intent: None }, cx)
2644//                 .is_none())
2645//         });
2646//     }
2647
2648//     #[gpui::test]
2649//     async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
2650//         cx.foreground().forbid_parking();
2651//         init_test(cx);
2652//         let fs = FakeFs::new(cx.background());
2653
2654//         let project = Project::test(fs, None, cx).await;
2655//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2656//         let workspace = window.root(cx);
2657//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2658
2659//         // 1. Add with a destination index
2660//         //   a. Add before the active item
2661//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
2662//         pane.update(cx, |pane, cx| {
2663//             pane.add_item(
2664//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2665//                 false,
2666//                 false,
2667//                 Some(0),
2668//                 cx,
2669//             );
2670//         });
2671//         assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2672
2673//         //   b. Add after the active item
2674//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
2675//         pane.update(cx, |pane, cx| {
2676//             pane.add_item(
2677//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2678//                 false,
2679//                 false,
2680//                 Some(2),
2681//                 cx,
2682//             );
2683//         });
2684//         assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2685
2686//         //   c. Add at the end of the item list (including off the length)
2687//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
2688//         pane.update(cx, |pane, cx| {
2689//             pane.add_item(
2690//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2691//                 false,
2692//                 false,
2693//                 Some(5),
2694//                 cx,
2695//             );
2696//         });
2697//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2698
2699//         // 2. Add without a destination index
2700//         //   a. Add with active item at the start of the item list
2701//         set_labeled_items(&pane, ["A*", "B", "C"], cx);
2702//         pane.update(cx, |pane, cx| {
2703//             pane.add_item(
2704//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2705//                 false,
2706//                 false,
2707//                 None,
2708//                 cx,
2709//             );
2710//         });
2711//         set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
2712
2713//         //   b. Add with active item at the end of the item list
2714//         set_labeled_items(&pane, ["A", "B", "C*"], cx);
2715//         pane.update(cx, |pane, cx| {
2716//             pane.add_item(
2717//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2718//                 false,
2719//                 false,
2720//                 None,
2721//                 cx,
2722//             );
2723//         });
2724//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2725//     }
2726
2727//     #[gpui::test]
2728//     async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
2729//         cx.foreground().forbid_parking();
2730//         init_test(cx);
2731//         let fs = FakeFs::new(cx.background());
2732
2733//         let project = Project::test(fs, None, cx).await;
2734//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2735//         let workspace = window.root(cx);
2736//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2737
2738//         // 1. Add with a destination index
2739//         //   1a. Add before the active item
2740//         let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2741//         pane.update(cx, |pane, cx| {
2742//             pane.add_item(d, false, false, Some(0), cx);
2743//         });
2744//         assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2745
2746//         //   1b. Add after the active item
2747//         let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2748//         pane.update(cx, |pane, cx| {
2749//             pane.add_item(d, false, false, Some(2), cx);
2750//         });
2751//         assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2752
2753//         //   1c. Add at the end of the item list (including off the length)
2754//         let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2755//         pane.update(cx, |pane, cx| {
2756//             pane.add_item(a, false, false, Some(5), cx);
2757//         });
2758//         assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2759
2760//         //   1d. Add same item to active index
2761//         let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
2762//         pane.update(cx, |pane, cx| {
2763//             pane.add_item(b, false, false, Some(1), cx);
2764//         });
2765//         assert_item_labels(&pane, ["A", "B*", "C"], cx);
2766
2767//         //   1e. Add item to index after same item in last position
2768//         let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
2769//         pane.update(cx, |pane, cx| {
2770//             pane.add_item(c, false, false, Some(2), cx);
2771//         });
2772//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
2773
2774//         // 2. Add without a destination index
2775//         //   2a. Add with active item at the start of the item list
2776//         let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
2777//         pane.update(cx, |pane, cx| {
2778//             pane.add_item(d, false, false, None, cx);
2779//         });
2780//         assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
2781
2782//         //   2b. Add with active item at the end of the item list
2783//         let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
2784//         pane.update(cx, |pane, cx| {
2785//             pane.add_item(a, false, false, None, cx);
2786//         });
2787//         assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2788
2789//         //   2c. Add active item to active item at end of list
2790//         let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
2791//         pane.update(cx, |pane, cx| {
2792//             pane.add_item(c, false, false, None, cx);
2793//         });
2794//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
2795
2796//         //   2d. Add active item to active item at start of list
2797//         let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
2798//         pane.update(cx, |pane, cx| {
2799//             pane.add_item(a, false, false, None, cx);
2800//         });
2801//         assert_item_labels(&pane, ["A*", "B", "C"], cx);
2802//     }
2803
2804//     #[gpui::test]
2805//     async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
2806//         cx.foreground().forbid_parking();
2807//         init_test(cx);
2808//         let fs = FakeFs::new(cx.background());
2809
2810//         let project = Project::test(fs, None, cx).await;
2811//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2812//         let workspace = window.root(cx);
2813//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2814
2815//         // singleton view
2816//         pane.update(cx, |pane, cx| {
2817//             let item = TestItem::new()
2818//                 .with_singleton(true)
2819//                 .with_label("buffer 1")
2820//                 .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
2821
2822//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2823//         });
2824//         assert_item_labels(&pane, ["buffer 1*"], cx);
2825
2826//         // new singleton view with the same project entry
2827//         pane.update(cx, |pane, cx| {
2828//             let item = TestItem::new()
2829//                 .with_singleton(true)
2830//                 .with_label("buffer 1")
2831//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2832
2833//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2834//         });
2835//         assert_item_labels(&pane, ["buffer 1*"], cx);
2836
2837//         // new singleton view with different project entry
2838//         pane.update(cx, |pane, cx| {
2839//             let item = TestItem::new()
2840//                 .with_singleton(true)
2841//                 .with_label("buffer 2")
2842//                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
2843//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2844//         });
2845//         assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
2846
2847//         // new multibuffer view with the same project entry
2848//         pane.update(cx, |pane, cx| {
2849//             let item = TestItem::new()
2850//                 .with_singleton(false)
2851//                 .with_label("multibuffer 1")
2852//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2853
2854//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2855//         });
2856//         assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
2857
2858//         // another multibuffer view with the same project entry
2859//         pane.update(cx, |pane, cx| {
2860//             let item = TestItem::new()
2861//                 .with_singleton(false)
2862//                 .with_label("multibuffer 1b")
2863//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2864
2865//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2866//         });
2867//         assert_item_labels(
2868//             &pane,
2869//             ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
2870//             cx,
2871//         );
2872//     }
2873
2874//     #[gpui::test]
2875//     async fn test_remove_item_ordering(cx: &mut TestAppContext) {
2876//         init_test(cx);
2877//         let fs = FakeFs::new(cx.background());
2878
2879//         let project = Project::test(fs, None, cx).await;
2880//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2881//         let workspace = window.root(cx);
2882//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2883
2884//         add_labeled_item(&pane, "A", false, cx);
2885//         add_labeled_item(&pane, "B", false, cx);
2886//         add_labeled_item(&pane, "C", false, cx);
2887//         add_labeled_item(&pane, "D", false, cx);
2888//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2889
2890//         pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
2891//         add_labeled_item(&pane, "1", false, cx);
2892//         assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
2893
2894//         pane.update(cx, |pane, cx| {
2895//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2896//         })
2897//         .unwrap()
2898//         .await
2899//         .unwrap();
2900//         assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
2901
2902//         pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
2903//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2904
2905//         pane.update(cx, |pane, cx| {
2906//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2907//         })
2908//         .unwrap()
2909//         .await
2910//         .unwrap();
2911//         assert_item_labels(&pane, ["A", "B*", "C"], cx);
2912
2913//         pane.update(cx, |pane, cx| {
2914//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2915//         })
2916//         .unwrap()
2917//         .await
2918//         .unwrap();
2919//         assert_item_labels(&pane, ["A", "C*"], cx);
2920
2921//         pane.update(cx, |pane, cx| {
2922//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2923//         })
2924//         .unwrap()
2925//         .await
2926//         .unwrap();
2927//         assert_item_labels(&pane, ["A*"], cx);
2928//     }
2929
2930//     #[gpui::test]
2931//     async fn test_close_inactive_items(cx: &mut TestAppContext) {
2932//         init_test(cx);
2933//         let fs = FakeFs::new(cx.background());
2934
2935//         let project = Project::test(fs, None, cx).await;
2936//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2937//         let workspace = window.root(cx);
2938//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2939
2940//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2941
2942//         pane.update(cx, |pane, cx| {
2943//             pane.close_inactive_items(&CloseInactiveItems, cx)
2944//         })
2945//         .unwrap()
2946//         .await
2947//         .unwrap();
2948//         assert_item_labels(&pane, ["C*"], cx);
2949//     }
2950
2951//     #[gpui::test]
2952//     async fn test_close_clean_items(cx: &mut TestAppContext) {
2953//         init_test(cx);
2954//         let fs = FakeFs::new(cx.background());
2955
2956//         let project = Project::test(fs, None, cx).await;
2957//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2958//         let workspace = window.root(cx);
2959//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2960
2961//         add_labeled_item(&pane, "A", true, cx);
2962//         add_labeled_item(&pane, "B", false, cx);
2963//         add_labeled_item(&pane, "C", true, cx);
2964//         add_labeled_item(&pane, "D", false, cx);
2965//         add_labeled_item(&pane, "E", false, cx);
2966//         assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
2967
2968//         pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
2969//             .unwrap()
2970//             .await
2971//             .unwrap();
2972//         assert_item_labels(&pane, ["A^", "C*^"], cx);
2973//     }
2974
2975//     #[gpui::test]
2976//     async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
2977//         init_test(cx);
2978//         let fs = FakeFs::new(cx.background());
2979
2980//         let project = Project::test(fs, None, cx).await;
2981//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2982//         let workspace = window.root(cx);
2983//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2984
2985//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2986
2987//         pane.update(cx, |pane, cx| {
2988//             pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
2989//         })
2990//         .unwrap()
2991//         .await
2992//         .unwrap();
2993//         assert_item_labels(&pane, ["C*", "D", "E"], cx);
2994//     }
2995
2996//     #[gpui::test]
2997//     async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
2998//         init_test(cx);
2999//         let fs = FakeFs::new(cx.background());
3000
3001//         let project = Project::test(fs, None, cx).await;
3002//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3003//         let workspace = window.root(cx);
3004//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3005
3006//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
3007
3008//         pane.update(cx, |pane, cx| {
3009//             pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
3010//         })
3011//         .unwrap()
3012//         .await
3013//         .unwrap();
3014//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
3015//     }
3016
3017//     #[gpui::test]
3018//     async fn test_close_all_items(cx: &mut TestAppContext) {
3019//         init_test(cx);
3020//         let fs = FakeFs::new(cx.background());
3021
3022//         let project = Project::test(fs, None, cx).await;
3023//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3024//         let workspace = window.root(cx);
3025//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3026
3027//         add_labeled_item(&pane, "A", false, cx);
3028//         add_labeled_item(&pane, "B", false, cx);
3029//         add_labeled_item(&pane, "C", false, cx);
3030//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
3031
3032//         pane.update(cx, |pane, cx| {
3033//             pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
3034//         })
3035//         .unwrap()
3036//         .await
3037//         .unwrap();
3038//         assert_item_labels(&pane, [], cx);
3039
3040//         add_labeled_item(&pane, "A", true, cx);
3041//         add_labeled_item(&pane, "B", true, cx);
3042//         add_labeled_item(&pane, "C", true, cx);
3043//         assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
3044
3045//         let save = pane
3046//             .update(cx, |pane, cx| {
3047//                 pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
3048//             })
3049//             .unwrap();
3050
3051//         cx.foreground().run_until_parked();
3052//         window.simulate_prompt_answer(2, cx);
3053//         save.await.unwrap();
3054//         assert_item_labels(&pane, [], cx);
3055//     }
3056
3057//     fn init_test(cx: &mut TestAppContext) {
3058//         cx.update(|cx| {
3059//             cx.set_global(SettingsStore::test(cx));
3060//             theme::init((), cx);
3061//             crate::init_settings(cx);
3062//             Project::init_settings(cx);
3063//         });
3064//     }
3065
3066//     fn add_labeled_item(
3067//         pane: &ViewHandle<Pane>,
3068//         label: &str,
3069//         is_dirty: bool,
3070//         cx: &mut TestAppContext,
3071//     ) -> Box<ViewHandle<TestItem>> {
3072//         pane.update(cx, |pane, cx| {
3073//             let labeled_item =
3074//                 Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
3075//             pane.add_item(labeled_item.clone(), false, false, None, cx);
3076//             labeled_item
3077//         })
3078//     }
3079
3080//     fn set_labeled_items<const COUNT: usize>(
3081//         pane: &ViewHandle<Pane>,
3082//         labels: [&str; COUNT],
3083//         cx: &mut TestAppContext,
3084//     ) -> [Box<ViewHandle<TestItem>>; COUNT] {
3085//         pane.update(cx, |pane, cx| {
3086//             pane.items.clear();
3087//             let mut active_item_index = 0;
3088
3089//             let mut index = 0;
3090//             let items = labels.map(|mut label| {
3091//                 if label.ends_with("*") {
3092//                     label = label.trim_end_matches("*");
3093//                     active_item_index = index;
3094//                 }
3095
3096//                 let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
3097//                 pane.add_item(labeled_item.clone(), false, false, None, cx);
3098//                 index += 1;
3099//                 labeled_item
3100//             });
3101
3102//             pane.activate_item(active_item_index, false, false, cx);
3103
3104//             items
3105//         })
3106//     }
3107
3108//     // Assert the item label, with the active item label suffixed with a '*'
3109//     fn assert_item_labels<const COUNT: usize>(
3110//         pane: &ViewHandle<Pane>,
3111//         expected_states: [&str; COUNT],
3112//         cx: &mut TestAppContext,
3113//     ) {
3114//         pane.read_with(cx, |pane, cx| {
3115//             let actual_states = pane
3116//                 .items
3117//                 .iter()
3118//                 .enumerate()
3119//                 .map(|(ix, item)| {
3120//                     let mut state = item
3121//                         .as_any()
3122//                         .downcast_ref::<TestItem>()
3123//                         .unwrap()
3124//                         .read(cx)
3125//                         .label
3126//                         .clone();
3127//                     if ix == pane.active_item_index {
3128//                         state.push('*');
3129//                     }
3130//                     if item.is_dirty(cx) {
3131//                         state.push('^');
3132//                     }
3133//                     state
3134//                 })
3135//                 .collect::<Vec<_>>();
3136
3137//             assert_eq!(
3138//                 actual_states, expected_states,
3139//                 "pane items do not match expectation"
3140//             );
3141//         })
3142//     }
3143// }
3144
3145#[derive(Clone, Debug)]
3146struct DraggedTab {
3147    title: String,
3148}
3149
3150impl Render for DraggedTab {
3151    type Element = Div;
3152
3153    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3154        div().w_8().h_4().bg(gpui::red())
3155    }
3156}