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