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