pane.rs

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