pane.rs

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