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);
 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.as_any());
 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.as_any());
 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.as_any().clone().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.as_any());
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.as_any(), 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        self.toolbar.update(cx, |toolbar, cx| {
1607            toolbar.pane_focus_update(true, cx);
1608        });
1609
1610        if let Some(active_item) = self.active_item() {
1611            if cx.is_self_focused() {
1612                // Pane was focused directly. We need to either focus a view inside the active item,
1613                // or focus the active item itself
1614                if let Some(weak_last_focused_view) =
1615                    self.last_focused_view_by_item.get(&active_item.id())
1616                {
1617                    if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
1618                        cx.focus(&last_focused_view);
1619                        return;
1620                    } else {
1621                        self.last_focused_view_by_item.remove(&active_item.id());
1622                    }
1623                }
1624
1625                cx.focus(active_item.as_any());
1626            } else if focused != self.tab_bar_context_menu.handle {
1627                self.last_focused_view_by_item
1628                    .insert(active_item.id(), focused.downgrade());
1629            }
1630        }
1631    }
1632
1633    fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
1634        self.toolbar.update(cx, |toolbar, cx| {
1635            toolbar.pane_focus_update(false, cx);
1636        });
1637    }
1638
1639    fn keymap_context(&self, _: &AppContext) -> KeymapContext {
1640        let mut keymap = Self::default_keymap_context();
1641        if self.docked.is_some() {
1642            keymap.add_identifier("docked");
1643        }
1644        keymap
1645    }
1646}
1647
1648fn render_tab_bar_button<A: Action + Clone>(
1649    index: usize,
1650    icon: &'static str,
1651    cx: &mut RenderContext<Pane>,
1652    action: A,
1653    context_menu: Option<ViewHandle<ContextMenu>>,
1654) -> ElementBox {
1655    enum TabBarButton {}
1656
1657    Stack::new()
1658        .with_child(
1659            MouseEventHandler::<TabBarButton>::new(index, cx, |mouse_state, cx| {
1660                let theme = &cx.global::<Settings>().theme.workspace.tab_bar;
1661                let style = theme.pane_button.style_for(mouse_state, false);
1662                Svg::new(icon)
1663                    .with_color(style.color)
1664                    .constrained()
1665                    .with_width(style.icon_width)
1666                    .aligned()
1667                    .constrained()
1668                    .with_width(style.button_width)
1669                    .with_height(style.button_width)
1670                    .boxed()
1671            })
1672            .with_cursor_style(CursorStyle::PointingHand)
1673            .on_click(MouseButton::Left, move |_, cx| {
1674                cx.dispatch_action(action.clone());
1675            })
1676            .boxed(),
1677        )
1678        .with_children(
1679            context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right().boxed()),
1680        )
1681        .flex(1., false)
1682        .boxed()
1683}
1684
1685impl ItemNavHistory {
1686    pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut MutableAppContext) {
1687        self.history.borrow_mut().push(data, self.item.clone(), cx);
1688    }
1689
1690    pub fn pop_backward(&self, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
1691        self.history.borrow_mut().pop(NavigationMode::GoingBack, cx)
1692    }
1693
1694    pub fn pop_forward(&self, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
1695        self.history
1696            .borrow_mut()
1697            .pop(NavigationMode::GoingForward, cx)
1698    }
1699}
1700
1701impl NavHistory {
1702    fn set_mode(&mut self, mode: NavigationMode) {
1703        self.mode = mode;
1704    }
1705
1706    fn disable(&mut self) {
1707        self.mode = NavigationMode::Disabled;
1708    }
1709
1710    fn enable(&mut self) {
1711        self.mode = NavigationMode::Normal;
1712    }
1713
1714    fn pop(&mut self, mode: NavigationMode, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
1715        let entry = match mode {
1716            NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
1717                return None
1718            }
1719            NavigationMode::GoingBack => &mut self.backward_stack,
1720            NavigationMode::GoingForward => &mut self.forward_stack,
1721            NavigationMode::ReopeningClosedItem => &mut self.closed_stack,
1722        }
1723        .pop_back();
1724        if entry.is_some() {
1725            self.did_update(cx);
1726        }
1727        entry
1728    }
1729
1730    fn push<D: 'static + Any>(
1731        &mut self,
1732        data: Option<D>,
1733        item: Rc<dyn WeakItemHandle>,
1734        cx: &mut MutableAppContext,
1735    ) {
1736        match self.mode {
1737            NavigationMode::Disabled => {}
1738            NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
1739                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1740                    self.backward_stack.pop_front();
1741                }
1742                self.backward_stack.push_back(NavigationEntry {
1743                    item,
1744                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1745                });
1746                self.forward_stack.clear();
1747            }
1748            NavigationMode::GoingBack => {
1749                if self.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1750                    self.forward_stack.pop_front();
1751                }
1752                self.forward_stack.push_back(NavigationEntry {
1753                    item,
1754                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1755                });
1756            }
1757            NavigationMode::GoingForward => {
1758                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1759                    self.backward_stack.pop_front();
1760                }
1761                self.backward_stack.push_back(NavigationEntry {
1762                    item,
1763                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1764                });
1765            }
1766            NavigationMode::ClosingItem => {
1767                if self.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1768                    self.closed_stack.pop_front();
1769                }
1770                self.closed_stack.push_back(NavigationEntry {
1771                    item,
1772                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1773                });
1774            }
1775        }
1776        self.did_update(cx);
1777    }
1778
1779    fn did_update(&self, cx: &mut MutableAppContext) {
1780        if let Some(pane) = self.pane.upgrade(cx) {
1781            cx.defer(move |cx| pane.update(cx, |pane, cx| pane.history_updated(cx)));
1782        }
1783    }
1784}
1785
1786pub struct PaneBackdrop {
1787    child_view: usize,
1788    child: ElementBox,
1789}
1790impl PaneBackdrop {
1791    pub fn new(pane_item_view: usize, child: ElementBox) -> Self {
1792        PaneBackdrop {
1793            child,
1794            child_view: pane_item_view,
1795        }
1796    }
1797}
1798
1799impl Element for PaneBackdrop {
1800    type LayoutState = ();
1801
1802    type PaintState = ();
1803
1804    fn layout(
1805        &mut self,
1806        constraint: gpui::SizeConstraint,
1807        cx: &mut gpui::LayoutContext,
1808    ) -> (Vector2F, Self::LayoutState) {
1809        let size = self.child.layout(constraint, cx);
1810        (size, ())
1811    }
1812
1813    fn paint(
1814        &mut self,
1815        bounds: RectF,
1816        visible_bounds: RectF,
1817        _: &mut Self::LayoutState,
1818        cx: &mut gpui::PaintContext,
1819    ) -> Self::PaintState {
1820        let background = cx.global::<Settings>().theme.editor.background;
1821
1822        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
1823
1824        cx.scene.push_quad(gpui::Quad {
1825            bounds: RectF::new(bounds.origin(), bounds.size()),
1826            background: Some(background),
1827            ..Default::default()
1828        });
1829
1830        let child_view_id = self.child_view;
1831        cx.scene.push_mouse_region(
1832            MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
1833                gpui::MouseButton::Left,
1834                move |_, cx| {
1835                    let window_id = cx.window_id;
1836                    cx.focus(window_id, Some(child_view_id))
1837                },
1838            ),
1839        );
1840
1841        cx.paint_layer(Some(bounds), |cx| {
1842            self.child.paint(bounds.origin(), visible_bounds, cx)
1843        })
1844    }
1845
1846    fn rect_for_text_range(
1847        &self,
1848        range_utf16: std::ops::Range<usize>,
1849        _bounds: RectF,
1850        _visible_bounds: RectF,
1851        _layout: &Self::LayoutState,
1852        _paint: &Self::PaintState,
1853        cx: &gpui::MeasurementContext,
1854    ) -> Option<RectF> {
1855        self.child.rect_for_text_range(range_utf16, cx)
1856    }
1857
1858    fn debug(
1859        &self,
1860        _bounds: RectF,
1861        _layout: &Self::LayoutState,
1862        _paint: &Self::PaintState,
1863        cx: &gpui::DebugContext,
1864    ) -> serde_json::Value {
1865        gpui::json::json!({
1866            "type": "Pane Back Drop",
1867            "view": self.child_view,
1868            "child": self.child.debug(cx),
1869        })
1870    }
1871}
1872
1873#[cfg(test)]
1874mod tests {
1875    use std::sync::Arc;
1876
1877    use super::*;
1878    use crate::item::test::{TestItem, TestProjectItem};
1879    use gpui::{executor::Deterministic, TestAppContext};
1880    use project::FakeFs;
1881
1882    #[gpui::test]
1883    async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
1884        cx.foreground().forbid_parking();
1885        Settings::test_async(cx);
1886        let fs = FakeFs::new(cx.background());
1887
1888        let project = Project::test(fs, None, cx).await;
1889        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1890        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1891
1892        // 1. Add with a destination index
1893        //   a. Add before the active item
1894        set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
1895        workspace.update(cx, |workspace, cx| {
1896            Pane::add_item(
1897                workspace,
1898                &pane,
1899                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1900                false,
1901                false,
1902                Some(0),
1903                cx,
1904            );
1905        });
1906        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
1907
1908        //   b. Add after the active item
1909        set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
1910        workspace.update(cx, |workspace, cx| {
1911            Pane::add_item(
1912                workspace,
1913                &pane,
1914                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1915                false,
1916                false,
1917                Some(2),
1918                cx,
1919            );
1920        });
1921        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
1922
1923        //   c. Add at the end of the item list (including off the length)
1924        set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
1925        workspace.update(cx, |workspace, cx| {
1926            Pane::add_item(
1927                workspace,
1928                &pane,
1929                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1930                false,
1931                false,
1932                Some(5),
1933                cx,
1934            );
1935        });
1936        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
1937
1938        // 2. Add without a destination index
1939        //   a. Add with active item at the start of the item list
1940        set_labeled_items(&workspace, &pane, ["A*", "B", "C"], cx);
1941        workspace.update(cx, |workspace, cx| {
1942            Pane::add_item(
1943                workspace,
1944                &pane,
1945                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1946                false,
1947                false,
1948                None,
1949                cx,
1950            );
1951        });
1952        set_labeled_items(&workspace, &pane, ["A", "D*", "B", "C"], cx);
1953
1954        //   b. Add with active item at the end of the item list
1955        set_labeled_items(&workspace, &pane, ["A", "B", "C*"], cx);
1956        workspace.update(cx, |workspace, cx| {
1957            Pane::add_item(
1958                workspace,
1959                &pane,
1960                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1961                false,
1962                false,
1963                None,
1964                cx,
1965            );
1966        });
1967        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
1968    }
1969
1970    #[gpui::test]
1971    async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
1972        cx.foreground().forbid_parking();
1973        Settings::test_async(cx);
1974        let fs = FakeFs::new(cx.background());
1975
1976        let project = Project::test(fs, None, cx).await;
1977        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1978        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1979
1980        // 1. Add with a destination index
1981        //   1a. Add before the active item
1982        let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx);
1983        workspace.update(cx, |workspace, cx| {
1984            Pane::add_item(workspace, &pane, d, false, false, Some(0), cx);
1985        });
1986        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
1987
1988        //   1b. Add after the active item
1989        let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx);
1990        workspace.update(cx, |workspace, cx| {
1991            Pane::add_item(workspace, &pane, d, false, false, Some(2), cx);
1992        });
1993        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
1994
1995        //   1c. Add at the end of the item list (including off the length)
1996        let [a, _, _, _] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx);
1997        workspace.update(cx, |workspace, cx| {
1998            Pane::add_item(workspace, &pane, a, false, false, Some(5), cx);
1999        });
2000        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2001
2002        //   1d. Add same item to active index
2003        let [_, b, _] = set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
2004        workspace.update(cx, |workspace, cx| {
2005            Pane::add_item(workspace, &pane, b, false, false, Some(1), cx);
2006        });
2007        assert_item_labels(&pane, ["A", "B*", "C"], cx);
2008
2009        //   1e. Add item to index after same item in last position
2010        let [_, _, c] = set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
2011        workspace.update(cx, |workspace, cx| {
2012            Pane::add_item(workspace, &pane, c, false, false, Some(2), cx);
2013        });
2014        assert_item_labels(&pane, ["A", "B", "C*"], cx);
2015
2016        // 2. Add without a destination index
2017        //   2a. Add with active item at the start of the item list
2018        let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A*", "B", "C", "D"], cx);
2019        workspace.update(cx, |workspace, cx| {
2020            Pane::add_item(workspace, &pane, d, false, false, None, cx);
2021        });
2022        assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
2023
2024        //   2b. Add with active item at the end of the item list
2025        let [a, _, _, _] = set_labeled_items(&workspace, &pane, ["A", "B", "C", "D*"], cx);
2026        workspace.update(cx, |workspace, cx| {
2027            Pane::add_item(workspace, &pane, a, false, false, None, cx);
2028        });
2029        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2030
2031        //   2c. Add active item to active item at end of list
2032        let [_, _, c] = set_labeled_items(&workspace, &pane, ["A", "B", "C*"], cx);
2033        workspace.update(cx, |workspace, cx| {
2034            Pane::add_item(workspace, &pane, c, false, false, None, cx);
2035        });
2036        assert_item_labels(&pane, ["A", "B", "C*"], cx);
2037
2038        //   2d. Add active item to active item at start of list
2039        let [a, _, _] = set_labeled_items(&workspace, &pane, ["A*", "B", "C"], cx);
2040        workspace.update(cx, |workspace, cx| {
2041            Pane::add_item(workspace, &pane, a, false, false, None, cx);
2042        });
2043        assert_item_labels(&pane, ["A*", "B", "C"], cx);
2044    }
2045
2046    #[gpui::test]
2047    async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
2048        cx.foreground().forbid_parking();
2049        Settings::test_async(cx);
2050        let fs = FakeFs::new(cx.background());
2051
2052        let project = Project::test(fs, None, cx).await;
2053        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2054        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2055
2056        // singleton view
2057        workspace.update(cx, |workspace, cx| {
2058            let item = TestItem::new()
2059                .with_singleton(true)
2060                .with_label("buffer 1")
2061                .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
2062
2063            Pane::add_item(
2064                workspace,
2065                &pane,
2066                Box::new(cx.add_view(|_| item)),
2067                false,
2068                false,
2069                None,
2070                cx,
2071            );
2072        });
2073        assert_item_labels(&pane, ["buffer 1*"], cx);
2074
2075        // new singleton view with the same project entry
2076        workspace.update(cx, |workspace, cx| {
2077            let item = TestItem::new()
2078                .with_singleton(true)
2079                .with_label("buffer 1")
2080                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2081
2082            Pane::add_item(
2083                workspace,
2084                &pane,
2085                Box::new(cx.add_view(|_| item)),
2086                false,
2087                false,
2088                None,
2089                cx,
2090            );
2091        });
2092        assert_item_labels(&pane, ["buffer 1*"], cx);
2093
2094        // new singleton view with different project entry
2095        workspace.update(cx, |workspace, cx| {
2096            let item = TestItem::new()
2097                .with_singleton(true)
2098                .with_label("buffer 2")
2099                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
2100
2101            Pane::add_item(
2102                workspace,
2103                &pane,
2104                Box::new(cx.add_view(|_| item)),
2105                false,
2106                false,
2107                None,
2108                cx,
2109            );
2110        });
2111        assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
2112
2113        // new multibuffer view with the same project entry
2114        workspace.update(cx, |workspace, cx| {
2115            let item = TestItem::new()
2116                .with_singleton(false)
2117                .with_label("multibuffer 1")
2118                .with_project_items(&[TestProjectItem::new(1, "1.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", "buffer 2", "multibuffer 1*"], cx);
2131
2132        // another multibuffer view with the same project entry
2133        workspace.update(cx, |workspace, cx| {
2134            let item = TestItem::new()
2135                .with_singleton(false)
2136                .with_label("multibuffer 1b")
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(
2150            &pane,
2151            ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
2152            cx,
2153        );
2154    }
2155
2156    #[gpui::test]
2157    async fn test_remove_item_ordering(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
2158        Settings::test_async(cx);
2159        let fs = FakeFs::new(cx.background());
2160
2161        let project = Project::test(fs, None, cx).await;
2162        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2163        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2164
2165        add_labled_item(&workspace, &pane, "A", cx);
2166        add_labled_item(&workspace, &pane, "B", cx);
2167        add_labled_item(&workspace, &pane, "C", cx);
2168        add_labled_item(&workspace, &pane, "D", cx);
2169        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2170
2171        pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
2172        add_labled_item(&workspace, &pane, "1", cx);
2173        assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
2174
2175        workspace.update(cx, |workspace, cx| {
2176            Pane::close_active_item(workspace, &CloseActiveItem, cx);
2177        });
2178        deterministic.run_until_parked();
2179        assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
2180
2181        pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
2182        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2183
2184        workspace.update(cx, |workspace, cx| {
2185            Pane::close_active_item(workspace, &CloseActiveItem, cx);
2186        });
2187        deterministic.run_until_parked();
2188        assert_item_labels(&pane, ["A", "B*", "C"], cx);
2189
2190        workspace.update(cx, |workspace, cx| {
2191            Pane::close_active_item(workspace, &CloseActiveItem, cx);
2192        });
2193        deterministic.run_until_parked();
2194        assert_item_labels(&pane, ["A", "C*"], cx);
2195
2196        workspace.update(cx, |workspace, cx| {
2197            Pane::close_active_item(workspace, &CloseActiveItem, cx);
2198        });
2199        deterministic.run_until_parked();
2200        assert_item_labels(&pane, ["A*"], cx);
2201    }
2202
2203    fn add_labled_item(
2204        workspace: &ViewHandle<Workspace>,
2205        pane: &ViewHandle<Pane>,
2206        label: &str,
2207        cx: &mut TestAppContext,
2208    ) -> Box<ViewHandle<TestItem>> {
2209        workspace.update(cx, |workspace, cx| {
2210            let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
2211
2212            Pane::add_item(
2213                workspace,
2214                pane,
2215                labeled_item.clone(),
2216                false,
2217                false,
2218                None,
2219                cx,
2220            );
2221
2222            labeled_item
2223        })
2224    }
2225
2226    fn set_labeled_items<const COUNT: usize>(
2227        workspace: &ViewHandle<Workspace>,
2228        pane: &ViewHandle<Pane>,
2229        labels: [&str; COUNT],
2230        cx: &mut TestAppContext,
2231    ) -> [Box<ViewHandle<TestItem>>; COUNT] {
2232        pane.update(cx, |pane, _| {
2233            pane.items.clear();
2234        });
2235
2236        workspace.update(cx, |workspace, cx| {
2237            let mut active_item_index = 0;
2238
2239            let mut index = 0;
2240            let items = labels.map(|mut label| {
2241                if label.ends_with("*") {
2242                    label = label.trim_end_matches("*");
2243                    active_item_index = index;
2244                }
2245
2246                let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
2247                Pane::add_item(
2248                    workspace,
2249                    pane,
2250                    labeled_item.clone(),
2251                    false,
2252                    false,
2253                    None,
2254                    cx,
2255                );
2256                index += 1;
2257                labeled_item
2258            });
2259
2260            pane.update(cx, |pane, cx| {
2261                pane.activate_item(active_item_index, false, false, cx)
2262            });
2263
2264            items
2265        })
2266    }
2267
2268    // Assert the item label, with the active item label suffixed with a '*'
2269    fn assert_item_labels<const COUNT: usize>(
2270        pane: &ViewHandle<Pane>,
2271        expected_states: [&str; COUNT],
2272        cx: &mut TestAppContext,
2273    ) {
2274        pane.read_with(cx, |pane, cx| {
2275            let actual_states = pane
2276                .items
2277                .iter()
2278                .enumerate()
2279                .map(|(ix, item)| {
2280                    let mut state = item
2281                        .as_any()
2282                        .downcast_ref::<TestItem>()
2283                        .unwrap()
2284                        .read(cx)
2285                        .label
2286                        .clone();
2287                    if ix == pane.active_item_index {
2288                        state.push('*');
2289                    }
2290                    state
2291                })
2292                .collect::<Vec<_>>();
2293
2294            assert_eq!(
2295                actual_states, expected_states,
2296                "pane items do not match expectation"
2297            );
2298        })
2299    }
2300}