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