pane.rs

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