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