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