pane.rs

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