pane.rs

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