pane.rs

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