pane.rs

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