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