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    fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
1309        let active_item = self
1310            .items
1311            .get(self.active_item_index)
1312            .map(|item| item.as_ref());
1313        self.toolbar.update(cx, |toolbar, cx| {
1314            toolbar.set_active_pane_item(active_item, cx);
1315        });
1316    }
1317
1318    fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
1319        let theme = theme::current(cx).clone();
1320
1321        let pane = cx.handle().downgrade();
1322        let autoscroll = if mem::take(&mut self.autoscroll) {
1323            Some(self.active_item_index)
1324        } else {
1325            None
1326        };
1327
1328        let pane_active = self.is_active;
1329
1330        enum Tabs {}
1331        let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
1332        for (ix, (item, detail)) in self
1333            .items
1334            .iter()
1335            .cloned()
1336            .zip(self.tab_details(cx))
1337            .enumerate()
1338        {
1339            let detail = if detail == 0 { None } else { Some(detail) };
1340            let tab_active = ix == self.active_item_index;
1341
1342            row.add_child({
1343                enum TabDragReceiver {}
1344                let mut receiver =
1345                    dragged_item_receiver::<TabDragReceiver, _, _>(ix, ix, true, None, cx, {
1346                        let item = item.clone();
1347                        let pane = pane.clone();
1348                        let detail = detail.clone();
1349
1350                        let theme = theme::current(cx).clone();
1351                        let mut tooltip_theme = theme.tooltip.clone();
1352                        tooltip_theme.max_text_width = None;
1353                        let tab_tooltip_text = item.tab_tooltip_text(cx).map(|a| a.to_string());
1354
1355                        move |mouse_state, cx| {
1356                            let tab_style =
1357                                theme.workspace.tab_bar.tab_style(pane_active, tab_active);
1358                            let hovered = mouse_state.hovered();
1359
1360                            enum Tab {}
1361                            let mouse_event_handler =
1362                                MouseEventHandler::<Tab, Pane>::new(ix, cx, |_, cx| {
1363                                    Self::render_tab(
1364                                        &item,
1365                                        pane.clone(),
1366                                        ix == 0,
1367                                        detail,
1368                                        hovered,
1369                                        tab_style,
1370                                        cx,
1371                                    )
1372                                })
1373                                .on_down(MouseButton::Left, move |_, this, cx| {
1374                                    this.activate_item(ix, true, true, cx);
1375                                })
1376                                .on_click(MouseButton::Middle, {
1377                                    let item_id = item.id();
1378                                    move |_, pane, cx| {
1379                                        let workspace = pane.workspace.clone();
1380                                        let pane = cx.weak_handle();
1381                                        cx.window_context().defer(move |cx| {
1382                                            if let Some((workspace, pane)) =
1383                                                workspace.upgrade(cx).zip(pane.upgrade(cx))
1384                                            {
1385                                                workspace.update(cx, |workspace, cx| {
1386                                                    Self::close_item_by_id(
1387                                                        workspace, pane, item_id, cx,
1388                                                    )
1389                                                    .detach_and_log_err(cx);
1390                                                });
1391                                            }
1392                                        });
1393                                    }
1394                                })
1395                                .on_down(
1396                                    MouseButton::Right,
1397                                    move |event, pane, cx| {
1398                                        pane.deploy_tab_context_menu(event.position, item.id(), cx);
1399                                    },
1400                                );
1401
1402                            if let Some(tab_tooltip_text) = tab_tooltip_text {
1403                                mouse_event_handler
1404                                    .with_tooltip::<Self>(
1405                                        ix,
1406                                        tab_tooltip_text,
1407                                        None,
1408                                        tooltip_theme,
1409                                        cx,
1410                                    )
1411                                    .into_any()
1412                            } else {
1413                                mouse_event_handler.into_any()
1414                            }
1415                        }
1416                    });
1417
1418                if !pane_active || !tab_active {
1419                    receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
1420                }
1421
1422                receiver.as_draggable(
1423                    DraggedItem {
1424                        item,
1425                        pane: pane.clone(),
1426                    },
1427                    {
1428                        let theme = theme::current(cx).clone();
1429
1430                        let detail = detail.clone();
1431                        move |dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
1432                            let tab_style = &theme.workspace.tab_bar.dragged_tab;
1433                            Self::render_dragged_tab(
1434                                &dragged_item.item,
1435                                dragged_item.pane.clone(),
1436                                false,
1437                                detail,
1438                                false,
1439                                &tab_style,
1440                                cx,
1441                            )
1442                        }
1443                    },
1444                )
1445            })
1446        }
1447
1448        // Use the inactive tab style along with the current pane's active status to decide how to render
1449        // the filler
1450        let filler_index = self.items.len();
1451        let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
1452        enum Filler {}
1453        row.add_child(
1454            dragged_item_receiver::<Filler, _, _>(0, filler_index, true, None, cx, |_, _| {
1455                Empty::new()
1456                    .contained()
1457                    .with_style(filler_style.container)
1458                    .with_border(filler_style.container.border)
1459            })
1460            .flex(1., true)
1461            .into_any_named("filler"),
1462        );
1463
1464        row
1465    }
1466
1467    fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
1468        let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
1469
1470        let mut tab_descriptions = HashMap::default();
1471        let mut done = false;
1472        while !done {
1473            done = true;
1474
1475            // Store item indices by their tab description.
1476            for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
1477                if let Some(description) = item.tab_description(*detail, cx) {
1478                    if *detail == 0
1479                        || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
1480                    {
1481                        tab_descriptions
1482                            .entry(description)
1483                            .or_insert(Vec::new())
1484                            .push(ix);
1485                    }
1486                }
1487            }
1488
1489            // If two or more items have the same tab description, increase their level
1490            // of detail and try again.
1491            for (_, item_ixs) in tab_descriptions.drain() {
1492                if item_ixs.len() > 1 {
1493                    done = false;
1494                    for ix in item_ixs {
1495                        tab_details[ix] += 1;
1496                    }
1497                }
1498            }
1499        }
1500
1501        tab_details
1502    }
1503
1504    fn render_tab(
1505        item: &Box<dyn ItemHandle>,
1506        pane: WeakViewHandle<Pane>,
1507        first: bool,
1508        detail: Option<usize>,
1509        hovered: bool,
1510        tab_style: &theme::Tab,
1511        cx: &mut ViewContext<Self>,
1512    ) -> AnyElement<Self> {
1513        let title = item.tab_content(detail, &tab_style, cx);
1514        Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
1515    }
1516
1517    fn render_dragged_tab(
1518        item: &Box<dyn ItemHandle>,
1519        pane: WeakViewHandle<Pane>,
1520        first: bool,
1521        detail: Option<usize>,
1522        hovered: bool,
1523        tab_style: &theme::Tab,
1524        cx: &mut ViewContext<Workspace>,
1525    ) -> AnyElement<Workspace> {
1526        let title = item.dragged_tab_content(detail, &tab_style, cx);
1527        Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
1528    }
1529
1530    fn render_tab_with_title<T: View>(
1531        title: AnyElement<T>,
1532        item: &Box<dyn ItemHandle>,
1533        pane: WeakViewHandle<Pane>,
1534        first: bool,
1535        hovered: bool,
1536        tab_style: &theme::Tab,
1537        cx: &mut ViewContext<T>,
1538    ) -> AnyElement<T> {
1539        let mut container = tab_style.container.clone();
1540        if first {
1541            container.border.left = false;
1542        }
1543
1544        Flex::row()
1545            .with_child({
1546                let diameter = 7.0;
1547                let icon_color = if item.has_conflict(cx) {
1548                    Some(tab_style.icon_conflict)
1549                } else if item.is_dirty(cx) {
1550                    Some(tab_style.icon_dirty)
1551                } else {
1552                    None
1553                };
1554
1555                Canvas::new(move |scene, bounds, _, _, _| {
1556                    if let Some(color) = icon_color {
1557                        let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
1558                        scene.push_quad(Quad {
1559                            bounds: square,
1560                            background: Some(color),
1561                            border: Default::default(),
1562                            corner_radius: diameter / 2.,
1563                        });
1564                    }
1565                })
1566                .constrained()
1567                .with_width(diameter)
1568                .with_height(diameter)
1569                .aligned()
1570            })
1571            .with_child(title.aligned().contained().with_style(ContainerStyle {
1572                margin: Margin {
1573                    left: tab_style.spacing,
1574                    right: tab_style.spacing,
1575                    ..Default::default()
1576                },
1577                ..Default::default()
1578            }))
1579            .with_child(
1580                if hovered {
1581                    let item_id = item.id();
1582                    enum TabCloseButton {}
1583                    let icon = Svg::new("icons/x_mark_8.svg");
1584                    MouseEventHandler::<TabCloseButton, _>::new(item_id, cx, |mouse_state, _| {
1585                        if mouse_state.hovered() {
1586                            icon.with_color(tab_style.icon_close_active)
1587                        } else {
1588                            icon.with_color(tab_style.icon_close)
1589                        }
1590                    })
1591                    .with_padding(Padding::uniform(4.))
1592                    .with_cursor_style(CursorStyle::PointingHand)
1593                    .on_click(MouseButton::Left, {
1594                        let pane = pane.clone();
1595                        move |_, _, cx| {
1596                            let pane = pane.clone();
1597                            cx.window_context().defer(move |cx| {
1598                                if let Some(pane) = pane.upgrade(cx) {
1599                                    if let Some(workspace) = pane.read(cx).workspace.upgrade(cx) {
1600                                        workspace.update(cx, |workspace, cx| {
1601                                            Self::close_item_by_id(workspace, pane, item_id, cx)
1602                                                .detach_and_log_err(cx);
1603                                        });
1604                                    }
1605                                }
1606                            });
1607                        }
1608                    })
1609                    .into_any_named("close-tab-icon")
1610                    .constrained()
1611                } else {
1612                    Empty::new().constrained()
1613                }
1614                .with_width(tab_style.close_icon_width)
1615                .aligned(),
1616            )
1617            .contained()
1618            .with_style(container)
1619            .constrained()
1620            .with_height(tab_style.height)
1621            .into_any()
1622    }
1623
1624    fn render_tab_bar_buttons(
1625        &mut self,
1626        theme: &Theme,
1627        cx: &mut ViewContext<Self>,
1628    ) -> AnyElement<Self> {
1629        Flex::row()
1630            // New menu
1631            .with_child(render_tab_bar_button(
1632                0,
1633                "icons/plus_12.svg",
1634                cx,
1635                |pane, cx| pane.deploy_new_menu(cx),
1636                self.tab_bar_context_menu
1637                    .handle_if_kind(TabBarContextMenuKind::New),
1638            ))
1639            .with_child(
1640                self.docked
1641                    .map(|anchor| {
1642                        // Add the dock menu button if this pane is a dock
1643                        let dock_icon = icon_for_dock_anchor(anchor);
1644
1645                        render_tab_bar_button(
1646                            1,
1647                            dock_icon,
1648                            cx,
1649                            |pane, cx| pane.deploy_dock_menu(cx),
1650                            self.tab_bar_context_menu
1651                                .handle_if_kind(TabBarContextMenuKind::Dock),
1652                        )
1653                    })
1654                    .unwrap_or_else(|| {
1655                        // Add the split menu if this pane is not a dock
1656                        render_tab_bar_button(
1657                            2,
1658                            "icons/split_12.svg",
1659                            cx,
1660                            |pane, cx| pane.deploy_split_menu(cx),
1661                            self.tab_bar_context_menu
1662                                .handle_if_kind(TabBarContextMenuKind::Split),
1663                        )
1664                    }),
1665            )
1666            // Add the close dock button if this pane is a dock
1667            .with_children(self.docked.map(|_| {
1668                render_tab_bar_button(
1669                    3,
1670                    "icons/x_mark_8.svg",
1671                    cx,
1672                    |this, cx| {
1673                        if let Some(workspace) = this.workspace.upgrade(cx) {
1674                            cx.window_context().defer(move |cx| {
1675                                workspace.update(cx, |workspace, cx| {
1676                                    Dock::hide_dock(workspace, &Default::default(), cx)
1677                                })
1678                            });
1679                        }
1680                    },
1681                    None,
1682                )
1683            }))
1684            .contained()
1685            .with_style(theme.workspace.tab_bar.pane_button_container)
1686            .flex(1., false)
1687            .into_any()
1688    }
1689
1690    fn render_blank_pane(
1691        &mut self,
1692        theme: &Theme,
1693        _cx: &mut ViewContext<Self>,
1694    ) -> AnyElement<Self> {
1695        let background = theme.workspace.background;
1696        Empty::new()
1697            .contained()
1698            .with_background_color(background)
1699            .into_any()
1700    }
1701}
1702
1703impl Entity for Pane {
1704    type Event = Event;
1705}
1706
1707impl View for Pane {
1708    fn ui_name() -> &'static str {
1709        "Pane"
1710    }
1711
1712    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
1713        enum MouseNavigationHandler {}
1714
1715        MouseEventHandler::<MouseNavigationHandler, _>::new(0, cx, |_, cx| {
1716            let active_item_index = self.active_item_index;
1717
1718            if let Some(active_item) = self.active_item() {
1719                Flex::column()
1720                    .with_child({
1721                        let theme = theme::current(cx).clone();
1722
1723                        let mut stack = Stack::new();
1724
1725                        enum TabBarEventHandler {}
1726                        stack.add_child(
1727                            MouseEventHandler::<TabBarEventHandler, _>::new(0, cx, |_, _| {
1728                                Empty::new()
1729                                    .contained()
1730                                    .with_style(theme.workspace.tab_bar.container)
1731                            })
1732                            .on_down(
1733                                MouseButton::Left,
1734                                move |_, this, cx| {
1735                                    this.activate_item(active_item_index, true, true, cx);
1736                                },
1737                            ),
1738                        );
1739
1740                        let mut tab_row = Flex::row()
1741                            .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
1742
1743                        if self.is_active {
1744                            tab_row.add_child(self.render_tab_bar_buttons(&theme, cx))
1745                        }
1746
1747                        stack.add_child(tab_row);
1748                        stack
1749                            .constrained()
1750                            .with_height(theme.workspace.tab_bar.height)
1751                            .flex(1., false)
1752                            .into_any_named("tab bar")
1753                    })
1754                    .with_child({
1755                        enum PaneContentTabDropTarget {}
1756                        dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
1757                            0,
1758                            self.active_item_index + 1,
1759                            false,
1760                            if self.docked.is_some() {
1761                                None
1762                            } else {
1763                                Some(100.)
1764                            },
1765                            cx,
1766                            {
1767                                let toolbar = self.toolbar.clone();
1768                                let toolbar_hidden = toolbar.read(cx).hidden();
1769                                move |_, cx| {
1770                                    Flex::column()
1771                                        .with_children(
1772                                            (!toolbar_hidden)
1773                                                .then(|| ChildView::new(&toolbar, cx).expanded()),
1774                                        )
1775                                        .with_child(
1776                                            ChildView::new(active_item.as_any(), cx).flex(1., true),
1777                                        )
1778                                }
1779                            },
1780                        )
1781                        .flex(1., true)
1782                    })
1783                    .with_child(ChildView::new(&self.tab_context_menu, cx))
1784                    .into_any()
1785            } else {
1786                enum EmptyPane {}
1787                let theme = theme::current(cx).clone();
1788
1789                dragged_item_receiver::<EmptyPane, _, _>(0, 0, false, None, cx, |_, cx| {
1790                    self.render_blank_pane(&theme, cx)
1791                })
1792                .on_down(MouseButton::Left, |_, _, cx| {
1793                    cx.focus_parent();
1794                })
1795                .into_any()
1796            }
1797        })
1798        .on_down(
1799            MouseButton::Navigate(NavigationDirection::Back),
1800            move |_, pane, cx| {
1801                if let Some(workspace) = pane.workspace.upgrade(cx) {
1802                    let pane = cx.weak_handle();
1803                    cx.window_context().defer(move |cx| {
1804                        workspace.update(cx, |workspace, cx| {
1805                            Pane::go_back(workspace, Some(pane), cx).detach_and_log_err(cx)
1806                        })
1807                    })
1808                }
1809            },
1810        )
1811        .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
1812            move |_, pane, cx| {
1813                if let Some(workspace) = pane.workspace.upgrade(cx) {
1814                    let pane = cx.weak_handle();
1815                    cx.window_context().defer(move |cx| {
1816                        workspace.update(cx, |workspace, cx| {
1817                            Pane::go_forward(workspace, Some(pane), cx).detach_and_log_err(cx)
1818                        })
1819                    })
1820                }
1821            }
1822        })
1823        .into_any_named("pane")
1824    }
1825
1826    fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
1827        self.has_focus = true;
1828        self.toolbar.update(cx, |toolbar, cx| {
1829            toolbar.pane_focus_update(true, cx);
1830        });
1831
1832        if let Some(active_item) = self.active_item() {
1833            if cx.is_self_focused() {
1834                // Pane was focused directly. We need to either focus a view inside the active item,
1835                // or focus the active item itself
1836                if let Some(weak_last_focused_view) =
1837                    self.last_focused_view_by_item.get(&active_item.id())
1838                {
1839                    if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
1840                        cx.focus(&last_focused_view);
1841                        return;
1842                    } else {
1843                        self.last_focused_view_by_item.remove(&active_item.id());
1844                    }
1845                }
1846
1847                cx.focus(active_item.as_any());
1848            } else if focused != self.tab_bar_context_menu.handle {
1849                self.last_focused_view_by_item
1850                    .insert(active_item.id(), focused.downgrade());
1851            }
1852        }
1853
1854        cx.emit(Event::Focus);
1855    }
1856
1857    fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
1858        self.has_focus = false;
1859        self.toolbar.update(cx, |toolbar, cx| {
1860            toolbar.pane_focus_update(false, cx);
1861        });
1862    }
1863
1864    fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
1865        Self::reset_to_default_keymap_context(keymap);
1866        if self.docked.is_some() {
1867            keymap.add_identifier("docked");
1868        }
1869    }
1870}
1871
1872fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>(
1873    index: usize,
1874    icon: &'static str,
1875    cx: &mut ViewContext<Pane>,
1876    on_click: F,
1877    context_menu: Option<ViewHandle<ContextMenu>>,
1878) -> AnyElement<Pane> {
1879    enum TabBarButton {}
1880
1881    Stack::new()
1882        .with_child(
1883            MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
1884                let theme = &theme::current(cx).workspace.tab_bar;
1885                let style = theme.pane_button.style_for(mouse_state, false);
1886                Svg::new(icon)
1887                    .with_color(style.color)
1888                    .constrained()
1889                    .with_width(style.icon_width)
1890                    .aligned()
1891                    .constrained()
1892                    .with_width(style.button_width)
1893                    .with_height(style.button_width)
1894            })
1895            .with_cursor_style(CursorStyle::PointingHand)
1896            .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)),
1897        )
1898        .with_children(
1899            context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
1900        )
1901        .flex(1., false)
1902        .into_any_named("tab bar button")
1903}
1904
1905impl ItemNavHistory {
1906    pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut WindowContext) {
1907        self.history.borrow_mut().push(data, self.item.clone(), cx);
1908    }
1909
1910    pub fn pop_backward(&self, cx: &mut WindowContext) -> Option<NavigationEntry> {
1911        self.history.borrow_mut().pop(NavigationMode::GoingBack, cx)
1912    }
1913
1914    pub fn pop_forward(&self, cx: &mut WindowContext) -> Option<NavigationEntry> {
1915        self.history
1916            .borrow_mut()
1917            .pop(NavigationMode::GoingForward, cx)
1918    }
1919}
1920
1921impl NavHistory {
1922    fn set_mode(&mut self, mode: NavigationMode) {
1923        self.mode = mode;
1924    }
1925
1926    fn disable(&mut self) {
1927        self.mode = NavigationMode::Disabled;
1928    }
1929
1930    fn enable(&mut self) {
1931        self.mode = NavigationMode::Normal;
1932    }
1933
1934    fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
1935        let entry = match mode {
1936            NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
1937                return None
1938            }
1939            NavigationMode::GoingBack => &mut self.backward_stack,
1940            NavigationMode::GoingForward => &mut self.forward_stack,
1941            NavigationMode::ReopeningClosedItem => &mut self.closed_stack,
1942        }
1943        .pop_back();
1944        if entry.is_some() {
1945            self.did_update(cx);
1946        }
1947        entry
1948    }
1949
1950    fn push<D: 'static + Any>(
1951        &mut self,
1952        data: Option<D>,
1953        item: Rc<dyn WeakItemHandle>,
1954        cx: &mut WindowContext,
1955    ) {
1956        match self.mode {
1957            NavigationMode::Disabled => {}
1958            NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
1959                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1960                    self.backward_stack.pop_front();
1961                }
1962                self.backward_stack.push_back(NavigationEntry {
1963                    item,
1964                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1965                    timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst),
1966                });
1967                self.forward_stack.clear();
1968            }
1969            NavigationMode::GoingBack => {
1970                if self.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1971                    self.forward_stack.pop_front();
1972                }
1973                self.forward_stack.push_back(NavigationEntry {
1974                    item,
1975                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1976                    timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst),
1977                });
1978            }
1979            NavigationMode::GoingForward => {
1980                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1981                    self.backward_stack.pop_front();
1982                }
1983                self.backward_stack.push_back(NavigationEntry {
1984                    item,
1985                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1986                    timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst),
1987                });
1988            }
1989            NavigationMode::ClosingItem => {
1990                if self.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1991                    self.closed_stack.pop_front();
1992                }
1993                self.closed_stack.push_back(NavigationEntry {
1994                    item,
1995                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1996                    timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst),
1997                });
1998            }
1999        }
2000        self.did_update(cx);
2001    }
2002
2003    fn did_update(&self, cx: &mut WindowContext) {
2004        if let Some(pane) = self.pane.upgrade(cx) {
2005            cx.defer(move |cx| {
2006                pane.update(cx, |pane, cx| pane.history_updated(cx));
2007            });
2008        }
2009    }
2010}
2011
2012impl PaneNavHistory {
2013    pub fn for_each_entry(
2014        &self,
2015        cx: &AppContext,
2016        mut f: impl FnMut(&NavigationEntry, ProjectPath),
2017    ) {
2018        let borrowed_history = self.0.borrow();
2019        borrowed_history
2020            .forward_stack
2021            .iter()
2022            .chain(borrowed_history.backward_stack.iter())
2023            .chain(borrowed_history.closed_stack.iter())
2024            .for_each(|entry| {
2025                if let Some(path) = borrowed_history.paths_by_item.get(&entry.item.id()) {
2026                    f(entry, path.clone());
2027                } else if let Some(item) = entry.item.upgrade(cx) {
2028                    let path = item.project_path(cx);
2029                    if let Some(path) = path {
2030                        f(entry, path);
2031                    }
2032                }
2033            })
2034    }
2035}
2036
2037pub struct PaneBackdrop<V: View> {
2038    child_view: usize,
2039    child: AnyElement<V>,
2040}
2041
2042impl<V: View> PaneBackdrop<V> {
2043    pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
2044        PaneBackdrop {
2045            child,
2046            child_view: pane_item_view,
2047        }
2048    }
2049}
2050
2051impl<V: View> Element<V> for PaneBackdrop<V> {
2052    type LayoutState = ();
2053
2054    type PaintState = ();
2055
2056    fn layout(
2057        &mut self,
2058        constraint: gpui::SizeConstraint,
2059        view: &mut V,
2060        cx: &mut LayoutContext<V>,
2061    ) -> (Vector2F, Self::LayoutState) {
2062        let size = self.child.layout(constraint, view, cx);
2063        (size, ())
2064    }
2065
2066    fn paint(
2067        &mut self,
2068        scene: &mut gpui::SceneBuilder,
2069        bounds: RectF,
2070        visible_bounds: RectF,
2071        _: &mut Self::LayoutState,
2072        view: &mut V,
2073        cx: &mut ViewContext<V>,
2074    ) -> Self::PaintState {
2075        let background = theme::current(cx).editor.background;
2076
2077        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
2078
2079        scene.push_quad(gpui::Quad {
2080            bounds: RectF::new(bounds.origin(), bounds.size()),
2081            background: Some(background),
2082            ..Default::default()
2083        });
2084
2085        let child_view_id = self.child_view;
2086        scene.push_mouse_region(
2087            MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
2088                gpui::platform::MouseButton::Left,
2089                move |_, _: &mut V, cx| {
2090                    let window_id = cx.window_id();
2091                    cx.app_context().focus(window_id, Some(child_view_id))
2092                },
2093            ),
2094        );
2095
2096        scene.paint_layer(Some(bounds), |scene| {
2097            self.child
2098                .paint(scene, bounds.origin(), visible_bounds, view, cx)
2099        })
2100    }
2101
2102    fn rect_for_text_range(
2103        &self,
2104        range_utf16: std::ops::Range<usize>,
2105        _bounds: RectF,
2106        _visible_bounds: RectF,
2107        _layout: &Self::LayoutState,
2108        _paint: &Self::PaintState,
2109        view: &V,
2110        cx: &gpui::ViewContext<V>,
2111    ) -> Option<RectF> {
2112        self.child.rect_for_text_range(range_utf16, view, cx)
2113    }
2114
2115    fn debug(
2116        &self,
2117        _bounds: RectF,
2118        _layout: &Self::LayoutState,
2119        _paint: &Self::PaintState,
2120        view: &V,
2121        cx: &gpui::ViewContext<V>,
2122    ) -> serde_json::Value {
2123        gpui::json::json!({
2124            "type": "Pane Back Drop",
2125            "view": self.child_view,
2126            "child": self.child.debug(view, cx),
2127        })
2128    }
2129}
2130
2131#[cfg(test)]
2132mod tests {
2133    use std::sync::Arc;
2134
2135    use super::*;
2136    use crate::item::test::{TestItem, TestProjectItem};
2137    use gpui::{executor::Deterministic, TestAppContext};
2138    use project::FakeFs;
2139    use settings::SettingsStore;
2140
2141    #[gpui::test]
2142    async fn test_remove_active_empty(cx: &mut TestAppContext) {
2143        init_test(cx);
2144        let fs = FakeFs::new(cx.background());
2145
2146        let project = Project::test(fs, None, cx).await;
2147        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2148
2149        workspace.update(cx, |workspace, cx| {
2150            assert!(Pane::close_active_item(workspace, &CloseActiveItem, cx).is_none())
2151        });
2152    }
2153
2154    #[gpui::test]
2155    async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
2156        cx.foreground().forbid_parking();
2157        init_test(cx);
2158        let fs = FakeFs::new(cx.background());
2159
2160        let project = Project::test(fs, None, cx).await;
2161        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2162        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2163
2164        // 1. Add with a destination index
2165        //   a. Add before the active item
2166        set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
2167        workspace.update(cx, |workspace, cx| {
2168            Pane::add_item(
2169                workspace,
2170                &pane,
2171                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2172                false,
2173                false,
2174                Some(0),
2175                cx,
2176            );
2177        });
2178        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2179
2180        //   b. Add after the active item
2181        set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
2182        workspace.update(cx, |workspace, cx| {
2183            Pane::add_item(
2184                workspace,
2185                &pane,
2186                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2187                false,
2188                false,
2189                Some(2),
2190                cx,
2191            );
2192        });
2193        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2194
2195        //   c. Add at the end of the item list (including off the length)
2196        set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
2197        workspace.update(cx, |workspace, cx| {
2198            Pane::add_item(
2199                workspace,
2200                &pane,
2201                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2202                false,
2203                false,
2204                Some(5),
2205                cx,
2206            );
2207        });
2208        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2209
2210        // 2. Add without a destination index
2211        //   a. Add with active item at the start of the item list
2212        set_labeled_items(&workspace, &pane, ["A*", "B", "C"], cx);
2213        workspace.update(cx, |workspace, cx| {
2214            Pane::add_item(
2215                workspace,
2216                &pane,
2217                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2218                false,
2219                false,
2220                None,
2221                cx,
2222            );
2223        });
2224        set_labeled_items(&workspace, &pane, ["A", "D*", "B", "C"], cx);
2225
2226        //   b. Add with active item at the end of the item list
2227        set_labeled_items(&workspace, &pane, ["A", "B", "C*"], cx);
2228        workspace.update(cx, |workspace, cx| {
2229            Pane::add_item(
2230                workspace,
2231                &pane,
2232                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2233                false,
2234                false,
2235                None,
2236                cx,
2237            );
2238        });
2239        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2240    }
2241
2242    #[gpui::test]
2243    async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
2244        cx.foreground().forbid_parking();
2245        init_test(cx);
2246        let fs = FakeFs::new(cx.background());
2247
2248        let project = Project::test(fs, None, cx).await;
2249        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2250        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2251
2252        // 1. Add with a destination index
2253        //   1a. Add before the active item
2254        let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx);
2255        workspace.update(cx, |workspace, cx| {
2256            Pane::add_item(workspace, &pane, d, false, false, Some(0), cx);
2257        });
2258        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2259
2260        //   1b. Add after the active item
2261        let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx);
2262        workspace.update(cx, |workspace, cx| {
2263            Pane::add_item(workspace, &pane, d, false, false, Some(2), cx);
2264        });
2265        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2266
2267        //   1c. Add at the end of the item list (including off the length)
2268        let [a, _, _, _] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx);
2269        workspace.update(cx, |workspace, cx| {
2270            Pane::add_item(workspace, &pane, a, false, false, Some(5), cx);
2271        });
2272        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2273
2274        //   1d. Add same item to active index
2275        let [_, b, _] = set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
2276        workspace.update(cx, |workspace, cx| {
2277            Pane::add_item(workspace, &pane, b, false, false, Some(1), cx);
2278        });
2279        assert_item_labels(&pane, ["A", "B*", "C"], cx);
2280
2281        //   1e. Add item to index after same item in last position
2282        let [_, _, c] = set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
2283        workspace.update(cx, |workspace, cx| {
2284            Pane::add_item(workspace, &pane, c, false, false, Some(2), cx);
2285        });
2286        assert_item_labels(&pane, ["A", "B", "C*"], cx);
2287
2288        // 2. Add without a destination index
2289        //   2a. Add with active item at the start of the item list
2290        let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A*", "B", "C", "D"], cx);
2291        workspace.update(cx, |workspace, cx| {
2292            Pane::add_item(workspace, &pane, d, false, false, None, cx);
2293        });
2294        assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
2295
2296        //   2b. Add with active item at the end of the item list
2297        let [a, _, _, _] = set_labeled_items(&workspace, &pane, ["A", "B", "C", "D*"], cx);
2298        workspace.update(cx, |workspace, cx| {
2299            Pane::add_item(workspace, &pane, a, false, false, None, cx);
2300        });
2301        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2302
2303        //   2c. Add active item to active item at end of list
2304        let [_, _, c] = set_labeled_items(&workspace, &pane, ["A", "B", "C*"], cx);
2305        workspace.update(cx, |workspace, cx| {
2306            Pane::add_item(workspace, &pane, c, false, false, None, cx);
2307        });
2308        assert_item_labels(&pane, ["A", "B", "C*"], cx);
2309
2310        //   2d. Add active item to active item at start of list
2311        let [a, _, _] = set_labeled_items(&workspace, &pane, ["A*", "B", "C"], cx);
2312        workspace.update(cx, |workspace, cx| {
2313            Pane::add_item(workspace, &pane, a, false, false, None, cx);
2314        });
2315        assert_item_labels(&pane, ["A*", "B", "C"], cx);
2316    }
2317
2318    #[gpui::test]
2319    async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
2320        cx.foreground().forbid_parking();
2321        init_test(cx);
2322        let fs = FakeFs::new(cx.background());
2323
2324        let project = Project::test(fs, None, cx).await;
2325        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2326        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2327
2328        // singleton view
2329        workspace.update(cx, |workspace, cx| {
2330            let item = TestItem::new()
2331                .with_singleton(true)
2332                .with_label("buffer 1")
2333                .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
2334
2335            Pane::add_item(
2336                workspace,
2337                &pane,
2338                Box::new(cx.add_view(|_| item)),
2339                false,
2340                false,
2341                None,
2342                cx,
2343            );
2344        });
2345        assert_item_labels(&pane, ["buffer 1*"], cx);
2346
2347        // new singleton view with the same project entry
2348        workspace.update(cx, |workspace, cx| {
2349            let item = TestItem::new()
2350                .with_singleton(true)
2351                .with_label("buffer 1")
2352                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2353
2354            Pane::add_item(
2355                workspace,
2356                &pane,
2357                Box::new(cx.add_view(|_| item)),
2358                false,
2359                false,
2360                None,
2361                cx,
2362            );
2363        });
2364        assert_item_labels(&pane, ["buffer 1*"], cx);
2365
2366        // new singleton view with different project entry
2367        workspace.update(cx, |workspace, cx| {
2368            let item = TestItem::new()
2369                .with_singleton(true)
2370                .with_label("buffer 2")
2371                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
2372
2373            Pane::add_item(
2374                workspace,
2375                &pane,
2376                Box::new(cx.add_view(|_| item)),
2377                false,
2378                false,
2379                None,
2380                cx,
2381            );
2382        });
2383        assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
2384
2385        // new multibuffer view with the same project entry
2386        workspace.update(cx, |workspace, cx| {
2387            let item = TestItem::new()
2388                .with_singleton(false)
2389                .with_label("multibuffer 1")
2390                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2391
2392            Pane::add_item(
2393                workspace,
2394                &pane,
2395                Box::new(cx.add_view(|_| item)),
2396                false,
2397                false,
2398                None,
2399                cx,
2400            );
2401        });
2402        assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
2403
2404        // another multibuffer view with the same project entry
2405        workspace.update(cx, |workspace, cx| {
2406            let item = TestItem::new()
2407                .with_singleton(false)
2408                .with_label("multibuffer 1b")
2409                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2410
2411            Pane::add_item(
2412                workspace,
2413                &pane,
2414                Box::new(cx.add_view(|_| item)),
2415                false,
2416                false,
2417                None,
2418                cx,
2419            );
2420        });
2421        assert_item_labels(
2422            &pane,
2423            ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
2424            cx,
2425        );
2426    }
2427
2428    #[gpui::test]
2429    async fn test_remove_item_ordering(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
2430        init_test(cx);
2431        let fs = FakeFs::new(cx.background());
2432
2433        let project = Project::test(fs, None, cx).await;
2434        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2435        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2436
2437        add_labeled_item(&workspace, &pane, "A", false, cx);
2438        add_labeled_item(&workspace, &pane, "B", false, cx);
2439        add_labeled_item(&workspace, &pane, "C", false, cx);
2440        add_labeled_item(&workspace, &pane, "D", false, cx);
2441        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2442
2443        pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
2444        add_labeled_item(&workspace, &pane, "1", false, cx);
2445        assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
2446
2447        workspace.update(cx, |workspace, cx| {
2448            Pane::close_active_item(workspace, &CloseActiveItem, cx);
2449        });
2450        deterministic.run_until_parked();
2451        assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
2452
2453        pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
2454        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2455
2456        workspace.update(cx, |workspace, cx| {
2457            Pane::close_active_item(workspace, &CloseActiveItem, cx);
2458        });
2459        deterministic.run_until_parked();
2460        assert_item_labels(&pane, ["A", "B*", "C"], cx);
2461
2462        workspace.update(cx, |workspace, cx| {
2463            Pane::close_active_item(workspace, &CloseActiveItem, cx);
2464        });
2465        deterministic.run_until_parked();
2466        assert_item_labels(&pane, ["A", "C*"], cx);
2467
2468        workspace.update(cx, |workspace, cx| {
2469            Pane::close_active_item(workspace, &CloseActiveItem, cx);
2470        });
2471        deterministic.run_until_parked();
2472        assert_item_labels(&pane, ["A*"], cx);
2473    }
2474
2475    #[gpui::test]
2476    async fn test_close_inactive_items(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
2477        init_test(cx);
2478        let fs = FakeFs::new(cx.background());
2479
2480        let project = Project::test(fs, None, cx).await;
2481        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2482        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2483
2484        set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx);
2485
2486        workspace.update(cx, |workspace, cx| {
2487            Pane::close_inactive_items(workspace, &CloseInactiveItems, cx);
2488        });
2489
2490        deterministic.run_until_parked();
2491        assert_item_labels(&pane, ["C*"], cx);
2492    }
2493
2494    #[gpui::test]
2495    async fn test_close_clean_items(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
2496        init_test(cx);
2497        let fs = FakeFs::new(cx.background());
2498
2499        let project = Project::test(fs, None, cx).await;
2500        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2501        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2502
2503        add_labeled_item(&workspace, &pane, "A", true, cx);
2504        add_labeled_item(&workspace, &pane, "B", false, cx);
2505        add_labeled_item(&workspace, &pane, "C", true, cx);
2506        add_labeled_item(&workspace, &pane, "D", false, cx);
2507        add_labeled_item(&workspace, &pane, "E", false, cx);
2508        assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
2509
2510        workspace.update(cx, |workspace, cx| {
2511            Pane::close_clean_items(workspace, &CloseCleanItems, cx);
2512        });
2513
2514        deterministic.run_until_parked();
2515        assert_item_labels(&pane, ["A^", "C*^"], cx);
2516    }
2517
2518    #[gpui::test]
2519    async fn test_close_items_to_the_left(
2520        deterministic: Arc<Deterministic>,
2521        cx: &mut TestAppContext,
2522    ) {
2523        init_test(cx);
2524        let fs = FakeFs::new(cx.background());
2525
2526        let project = Project::test(fs, None, cx).await;
2527        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2528        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2529
2530        set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx);
2531
2532        workspace.update(cx, |workspace, cx| {
2533            Pane::close_items_to_the_left(workspace, &CloseItemsToTheLeft, cx);
2534        });
2535
2536        deterministic.run_until_parked();
2537        assert_item_labels(&pane, ["C*", "D", "E"], cx);
2538    }
2539
2540    #[gpui::test]
2541    async fn test_close_items_to_the_right(
2542        deterministic: Arc<Deterministic>,
2543        cx: &mut TestAppContext,
2544    ) {
2545        init_test(cx);
2546        let fs = FakeFs::new(cx.background());
2547
2548        let project = Project::test(fs, None, cx).await;
2549        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2550        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2551
2552        set_labeled_items(&workspace, &pane, ["A", "B", "C*", "D", "E"], cx);
2553
2554        workspace.update(cx, |workspace, cx| {
2555            Pane::close_items_to_the_right(workspace, &CloseItemsToTheRight, cx);
2556        });
2557
2558        deterministic.run_until_parked();
2559        assert_item_labels(&pane, ["A", "B", "C*"], cx);
2560    }
2561
2562    #[gpui::test]
2563    async fn test_close_all_items(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
2564        init_test(cx);
2565        let fs = FakeFs::new(cx.background());
2566
2567        let project = Project::test(fs, None, cx).await;
2568        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2569        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2570
2571        add_labeled_item(&workspace, &pane, "A", false, cx);
2572        add_labeled_item(&workspace, &pane, "B", false, cx);
2573        add_labeled_item(&workspace, &pane, "C", false, cx);
2574        assert_item_labels(&pane, ["A", "B", "C*"], cx);
2575
2576        workspace.update(cx, |workspace, cx| {
2577            Pane::close_all_items(workspace, &CloseAllItems, cx);
2578        });
2579
2580        deterministic.run_until_parked();
2581        assert_item_labels(&pane, [], cx);
2582    }
2583
2584    fn init_test(cx: &mut TestAppContext) {
2585        cx.update(|cx| {
2586            cx.set_global(SettingsStore::test(cx));
2587            theme::init((), cx);
2588            crate::init_settings(cx);
2589        });
2590    }
2591
2592    fn add_labeled_item(
2593        workspace: &ViewHandle<Workspace>,
2594        pane: &ViewHandle<Pane>,
2595        label: &str,
2596        is_dirty: bool,
2597        cx: &mut TestAppContext,
2598    ) -> Box<ViewHandle<TestItem>> {
2599        workspace.update(cx, |workspace, cx| {
2600            let labeled_item =
2601                Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
2602
2603            Pane::add_item(
2604                workspace,
2605                pane,
2606                labeled_item.clone(),
2607                false,
2608                false,
2609                None,
2610                cx,
2611            );
2612
2613            labeled_item
2614        })
2615    }
2616
2617    fn set_labeled_items<const COUNT: usize>(
2618        workspace: &ViewHandle<Workspace>,
2619        pane: &ViewHandle<Pane>,
2620        labels: [&str; COUNT],
2621        cx: &mut TestAppContext,
2622    ) -> [Box<ViewHandle<TestItem>>; COUNT] {
2623        pane.update(cx, |pane, _| {
2624            pane.items.clear();
2625        });
2626
2627        workspace.update(cx, |workspace, cx| {
2628            let mut active_item_index = 0;
2629
2630            let mut index = 0;
2631            let items = labels.map(|mut label| {
2632                if label.ends_with("*") {
2633                    label = label.trim_end_matches("*");
2634                    active_item_index = index;
2635                }
2636
2637                let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
2638                Pane::add_item(
2639                    workspace,
2640                    pane,
2641                    labeled_item.clone(),
2642                    false,
2643                    false,
2644                    None,
2645                    cx,
2646                );
2647                index += 1;
2648                labeled_item
2649            });
2650
2651            pane.update(cx, |pane, cx| {
2652                pane.activate_item(active_item_index, false, false, cx)
2653            });
2654
2655            items
2656        })
2657    }
2658
2659    // Assert the item label, with the active item label suffixed with a '*'
2660    fn assert_item_labels<const COUNT: usize>(
2661        pane: &ViewHandle<Pane>,
2662        expected_states: [&str; COUNT],
2663        cx: &mut TestAppContext,
2664    ) {
2665        pane.read_with(cx, |pane, cx| {
2666            let actual_states = pane
2667                .items
2668                .iter()
2669                .enumerate()
2670                .map(|(ix, item)| {
2671                    let mut state = item
2672                        .as_any()
2673                        .downcast_ref::<TestItem>()
2674                        .unwrap()
2675                        .read(cx)
2676                        .label
2677                        .clone();
2678                    if ix == pane.active_item_index {
2679                        state.push('*');
2680                    }
2681                    if item.is_dirty(cx) {
2682                        state.push('^');
2683                    }
2684                    state
2685                })
2686                .collect::<Vec<_>>();
2687
2688            assert_eq!(
2689                actual_states, expected_states,
2690                "pane items do not match expectation"
2691            );
2692        })
2693    }
2694}