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