pane.rs

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