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