pane.rs

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