pane.rs

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