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