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