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