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