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