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