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