pane.rs

   1use super::{ItemHandle, SplitDirection};
   2use crate::{
   3    dock::{icon_for_dock_anchor, MoveDock, ToggleDock},
   4    toolbar::Toolbar,
   5    Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace,
   6};
   7use anyhow::Result;
   8use collections::{HashMap, HashSet, VecDeque};
   9use context_menu::{ContextMenu, ContextMenuItem};
  10use drag_and_drop::{DragAndDrop, Draggable};
  11use futures::StreamExt;
  12use gpui::{
  13    actions,
  14    color::Color,
  15    elements::*,
  16    geometry::{
  17        rect::RectF,
  18        vector::{vec2f, Vector2F},
  19    },
  20    impl_actions, impl_internal_actions,
  21    platform::{CursorStyle, NavigationDirection},
  22    Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
  23    ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
  24    ViewContext, ViewHandle, WeakViewHandle,
  25};
  26use project::{Project, ProjectEntryId, ProjectPath};
  27use serde::Deserialize;
  28use settings::{Autosave, DockAnchor, Settings};
  29use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
  30use theme::Theme;
  31use util::ResultExt;
  32
  33#[derive(Clone, Deserialize, PartialEq)]
  34pub struct ActivateItem(pub usize);
  35
  36actions!(
  37    pane,
  38    [
  39        ActivatePrevItem,
  40        ActivateNextItem,
  41        ActivateLastItem,
  42        CloseActiveItem,
  43        CloseInactiveItems,
  44        ReopenClosedItem,
  45        SplitLeft,
  46        SplitUp,
  47        SplitRight,
  48        SplitDown,
  49    ]
  50);
  51
  52#[derive(Clone, PartialEq)]
  53pub struct CloseItem {
  54    pub item_id: usize,
  55    pub pane: WeakViewHandle<Pane>,
  56}
  57
  58#[derive(Clone, PartialEq)]
  59pub struct MoveItem {
  60    pub item_id: usize,
  61    pub from: WeakViewHandle<Pane>,
  62    pub to: WeakViewHandle<Pane>,
  63    pub destination_index: usize,
  64}
  65
  66#[derive(Clone, Deserialize, PartialEq)]
  67pub struct GoBack {
  68    #[serde(skip_deserializing)]
  69    pub pane: Option<WeakViewHandle<Pane>>,
  70}
  71
  72#[derive(Clone, Deserialize, PartialEq)]
  73pub struct GoForward {
  74    #[serde(skip_deserializing)]
  75    pub pane: Option<WeakViewHandle<Pane>>,
  76}
  77
  78#[derive(Clone, PartialEq)]
  79pub struct DeploySplitMenu {
  80    position: Vector2F,
  81}
  82
  83#[derive(Clone, PartialEq)]
  84pub struct DeployDockMenu {
  85    position: Vector2F,
  86}
  87
  88#[derive(Clone, PartialEq)]
  89pub struct DeployNewMenu {
  90    position: Vector2F,
  91}
  92
  93impl_actions!(pane, [GoBack, GoForward, ActivateItem]);
  94impl_internal_actions!(
  95    pane,
  96    [
  97        CloseItem,
  98        DeploySplitMenu,
  99        DeployNewMenu,
 100        DeployDockMenu,
 101        MoveItem
 102    ]
 103);
 104
 105const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 106
 107pub fn init(cx: &mut MutableAppContext) {
 108    cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
 109        pane.activate_item(action.0, true, true, cx);
 110    });
 111    cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
 112        pane.activate_item(pane.items.len() - 1, true, true, cx);
 113    });
 114    cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
 115        pane.activate_prev_item(cx);
 116    });
 117    cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
 118        pane.activate_next_item(cx);
 119    });
 120    cx.add_async_action(Pane::close_active_item);
 121    cx.add_async_action(Pane::close_inactive_items);
 122    cx.add_async_action(|workspace: &mut Workspace, action: &CloseItem, cx| {
 123        let pane = action.pane.upgrade(cx)?;
 124        let task = Pane::close_item(workspace, pane, action.item_id, cx);
 125        Some(cx.foreground().spawn(async move {
 126            task.await?;
 127            Ok(())
 128        }))
 129    });
 130    cx.add_action(
 131        |workspace,
 132         MoveItem {
 133             from,
 134             to,
 135             item_id,
 136             destination_index,
 137         },
 138         cx| {
 139            // Get item handle to move
 140            let from = if let Some(from) = from.upgrade(cx) {
 141                from
 142            } else {
 143                return;
 144            };
 145
 146            // Add item to new pane at given index
 147            let to = if let Some(to) = to.upgrade(cx) {
 148                to
 149            } else {
 150                return;
 151            };
 152
 153            Pane::move_item(workspace, from, to, *item_id, *destination_index, cx)
 154        },
 155    );
 156    cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
 157    cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
 158    cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
 159    cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
 160    cx.add_action(Pane::deploy_split_menu);
 161    cx.add_action(Pane::deploy_new_menu);
 162    cx.add_action(Pane::deploy_dock_menu);
 163    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
 164        Pane::reopen_closed_item(workspace, cx).detach();
 165    });
 166    cx.add_action(|workspace: &mut Workspace, action: &GoBack, cx| {
 167        Pane::go_back(
 168            workspace,
 169            action
 170                .pane
 171                .as_ref()
 172                .and_then(|weak_handle| weak_handle.upgrade(cx)),
 173            cx,
 174        )
 175        .detach();
 176    });
 177    cx.add_action(|workspace: &mut Workspace, action: &GoForward, cx| {
 178        Pane::go_forward(
 179            workspace,
 180            action
 181                .pane
 182                .as_ref()
 183                .and_then(|weak_handle| weak_handle.upgrade(cx)),
 184            cx,
 185        )
 186        .detach();
 187    });
 188}
 189
 190#[derive(Debug)]
 191pub enum Event {
 192    Focused,
 193    ActivateItem { local: bool },
 194    Remove,
 195    RemoveItem { item_id: usize },
 196    Split(SplitDirection),
 197    ChangeItemTitle,
 198}
 199
 200pub struct Pane {
 201    items: Vec<Box<dyn ItemHandle>>,
 202    is_active: bool,
 203    active_item_index: usize,
 204    last_focused_view: Option<AnyWeakViewHandle>,
 205    autoscroll: bool,
 206    nav_history: Rc<RefCell<NavHistory>>,
 207    toolbar: ViewHandle<Toolbar>,
 208    tab_bar_context_menu: ViewHandle<ContextMenu>,
 209    docked: Option<DockAnchor>,
 210}
 211
 212pub struct ItemNavHistory {
 213    history: Rc<RefCell<NavHistory>>,
 214    item: Rc<dyn WeakItemHandle>,
 215}
 216
 217struct NavHistory {
 218    mode: NavigationMode,
 219    backward_stack: VecDeque<NavigationEntry>,
 220    forward_stack: VecDeque<NavigationEntry>,
 221    closed_stack: VecDeque<NavigationEntry>,
 222    paths_by_item: HashMap<usize, ProjectPath>,
 223    pane: WeakViewHandle<Pane>,
 224}
 225
 226#[derive(Copy, Clone)]
 227enum NavigationMode {
 228    Normal,
 229    GoingBack,
 230    GoingForward,
 231    ClosingItem,
 232    ReopeningClosedItem,
 233    Disabled,
 234}
 235
 236impl Default for NavigationMode {
 237    fn default() -> Self {
 238        Self::Normal
 239    }
 240}
 241
 242pub struct NavigationEntry {
 243    pub item: Rc<dyn WeakItemHandle>,
 244    pub data: Option<Box<dyn Any>>,
 245}
 246
 247struct DraggedItem {
 248    item: Box<dyn ItemHandle>,
 249    pane: WeakViewHandle<Pane>,
 250}
 251
 252pub enum ReorderBehavior {
 253    None,
 254    MoveAfterActive,
 255    MoveToIndex(usize),
 256}
 257
 258impl Pane {
 259    pub fn new(docked: Option<DockAnchor>, cx: &mut ViewContext<Self>) -> Self {
 260        let handle = cx.weak_handle();
 261        let context_menu = cx.add_view(ContextMenu::new);
 262        Self {
 263            items: Vec::new(),
 264            is_active: true,
 265            active_item_index: 0,
 266            last_focused_view: None,
 267            autoscroll: false,
 268            nav_history: Rc::new(RefCell::new(NavHistory {
 269                mode: NavigationMode::Normal,
 270                backward_stack: Default::default(),
 271                forward_stack: Default::default(),
 272                closed_stack: Default::default(),
 273                paths_by_item: Default::default(),
 274                pane: handle.clone(),
 275            })),
 276            toolbar: cx.add_view(|_| Toolbar::new(handle)),
 277            tab_bar_context_menu: context_menu,
 278            docked,
 279        }
 280    }
 281
 282    pub fn set_active(&mut self, is_active: bool, cx: &mut ViewContext<Self>) {
 283        self.is_active = is_active;
 284        cx.notify();
 285    }
 286
 287    pub fn set_docked(&mut self, docked: Option<DockAnchor>, cx: &mut ViewContext<Self>) {
 288        self.docked = docked;
 289        cx.notify();
 290    }
 291
 292    pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
 293        ItemNavHistory {
 294            history: self.nav_history.clone(),
 295            item: Rc::new(item.downgrade()),
 296        }
 297    }
 298
 299    pub fn go_back(
 300        workspace: &mut Workspace,
 301        pane: Option<ViewHandle<Pane>>,
 302        cx: &mut ViewContext<Workspace>,
 303    ) -> Task<()> {
 304        Self::navigate_history(
 305            workspace,
 306            pane.unwrap_or_else(|| workspace.active_pane().clone()),
 307            NavigationMode::GoingBack,
 308            cx,
 309        )
 310    }
 311
 312    pub fn go_forward(
 313        workspace: &mut Workspace,
 314        pane: Option<ViewHandle<Pane>>,
 315        cx: &mut ViewContext<Workspace>,
 316    ) -> Task<()> {
 317        Self::navigate_history(
 318            workspace,
 319            pane.unwrap_or_else(|| workspace.active_pane().clone()),
 320            NavigationMode::GoingForward,
 321            cx,
 322        )
 323    }
 324
 325    pub fn reopen_closed_item(
 326        workspace: &mut Workspace,
 327        cx: &mut ViewContext<Workspace>,
 328    ) -> Task<()> {
 329        Self::navigate_history(
 330            workspace,
 331            workspace.active_pane().clone(),
 332            NavigationMode::ReopeningClosedItem,
 333            cx,
 334        )
 335    }
 336
 337    pub fn disable_history(&mut self) {
 338        self.nav_history.borrow_mut().disable();
 339    }
 340
 341    pub fn enable_history(&mut self) {
 342        self.nav_history.borrow_mut().enable();
 343    }
 344
 345    pub fn can_navigate_backward(&self) -> bool {
 346        !self.nav_history.borrow().backward_stack.is_empty()
 347    }
 348
 349    pub fn can_navigate_forward(&self) -> bool {
 350        !self.nav_history.borrow().forward_stack.is_empty()
 351    }
 352
 353    fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
 354        self.toolbar.update(cx, |_, cx| cx.notify());
 355    }
 356
 357    fn navigate_history(
 358        workspace: &mut Workspace,
 359        pane: ViewHandle<Pane>,
 360        mode: NavigationMode,
 361        cx: &mut ViewContext<Workspace>,
 362    ) -> Task<()> {
 363        cx.focus(pane.clone());
 364
 365        let to_load = pane.update(cx, |pane, cx| {
 366            loop {
 367                // Retrieve the weak item handle from the history.
 368                let entry = pane.nav_history.borrow_mut().pop(mode, cx)?;
 369
 370                // If the item is still present in this pane, then activate it.
 371                if let Some(index) = entry
 372                    .item
 373                    .upgrade(cx)
 374                    .and_then(|v| pane.index_for_item(v.as_ref()))
 375                {
 376                    let prev_active_item_index = pane.active_item_index;
 377                    pane.nav_history.borrow_mut().set_mode(mode);
 378                    pane.activate_item(index, true, true, cx);
 379                    pane.nav_history
 380                        .borrow_mut()
 381                        .set_mode(NavigationMode::Normal);
 382
 383                    let mut navigated = prev_active_item_index != pane.active_item_index;
 384                    if let Some(data) = entry.data {
 385                        navigated |= pane.active_item()?.navigate(data, cx);
 386                    }
 387
 388                    if navigated {
 389                        break None;
 390                    }
 391                }
 392                // If the item is no longer present in this pane, then retrieve its
 393                // project path in order to reopen it.
 394                else {
 395                    break pane
 396                        .nav_history
 397                        .borrow()
 398                        .paths_by_item
 399                        .get(&entry.item.id())
 400                        .cloned()
 401                        .map(|project_path| (project_path, entry));
 402                }
 403            }
 404        });
 405
 406        if let Some((project_path, entry)) = to_load {
 407            // If the item was no longer present, then load it again from its previous path.
 408            let pane = pane.downgrade();
 409            let task = workspace.load_path(project_path, cx);
 410            cx.spawn(|workspace, mut cx| async move {
 411                let task = task.await;
 412                if let Some(pane) = pane.upgrade(&cx) {
 413                    let mut navigated = false;
 414                    if let Some((project_entry_id, build_item)) = task.log_err() {
 415                        let prev_active_item_id = pane.update(&mut cx, |pane, _| {
 416                            pane.nav_history.borrow_mut().set_mode(mode);
 417                            pane.active_item().map(|p| p.id())
 418                        });
 419
 420                        let item = workspace.update(&mut cx, |workspace, cx| {
 421                            Self::open_item(
 422                                workspace,
 423                                pane.clone(),
 424                                project_entry_id,
 425                                true,
 426                                cx,
 427                                build_item,
 428                            )
 429                        });
 430
 431                        pane.update(&mut cx, |pane, cx| {
 432                            navigated |= Some(item.id()) != prev_active_item_id;
 433                            pane.nav_history
 434                                .borrow_mut()
 435                                .set_mode(NavigationMode::Normal);
 436                            if let Some(data) = entry.data {
 437                                navigated |= item.navigate(data, cx);
 438                            }
 439                        });
 440                    }
 441
 442                    if !navigated {
 443                        workspace
 444                            .update(&mut cx, |workspace, cx| {
 445                                Self::navigate_history(workspace, pane, mode, cx)
 446                            })
 447                            .await;
 448                    }
 449                }
 450            })
 451        } else {
 452            Task::ready(())
 453        }
 454    }
 455
 456    pub(crate) fn open_item(
 457        workspace: &mut Workspace,
 458        pane: ViewHandle<Pane>,
 459        project_entry_id: ProjectEntryId,
 460        focus_item: bool,
 461        cx: &mut ViewContext<Workspace>,
 462        build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 463    ) -> Box<dyn ItemHandle> {
 464        let existing_item = pane.update(cx, |pane, cx| {
 465            for item in pane.items.iter() {
 466                if item.project_path(cx).is_some()
 467                    && item.project_entry_ids(cx).as_slice() == [project_entry_id]
 468                {
 469                    let item = item.boxed_clone();
 470                    return Some(item);
 471                }
 472            }
 473            None
 474        });
 475
 476        // Even if the item exists, we re-add it to reorder it after the active item.
 477        // We may revisit this behavior after adding an "activation history" for pane items.
 478        let item = existing_item.unwrap_or_else(|| pane.update(cx, |_, cx| build_item(cx)));
 479        Pane::add_item(workspace, &pane, item.clone(), true, focus_item, None, cx);
 480        item
 481    }
 482
 483    pub(crate) fn add_item(
 484        workspace: &mut Workspace,
 485        pane: &ViewHandle<Pane>,
 486        item: Box<dyn ItemHandle>,
 487        activate_pane: bool,
 488        focus_item: bool,
 489        destination_index: Option<usize>,
 490        cx: &mut ViewContext<Workspace>,
 491    ) {
 492        // If no destination index is specified, add or move the item after the active item.
 493        let mut insertion_index = {
 494            let pane = pane.read(cx);
 495            cmp::min(
 496                if let Some(destination_index) = destination_index {
 497                    destination_index
 498                } else {
 499                    pane.active_item_index + 1
 500                },
 501                pane.items.len(),
 502            )
 503        };
 504
 505        item.added_to_pane(workspace, pane.clone(), cx);
 506
 507        // Does the item already exist?
 508        let project_entry_id = if item.is_singleton(cx) {
 509            item.project_entry_ids(cx).get(0).copied()
 510        } else {
 511            None
 512        };
 513
 514        let existing_item_index = pane.read(cx).items.iter().position(|existing_item| {
 515            if existing_item.id() == item.id() {
 516                true
 517            } else if existing_item.is_singleton(cx) {
 518                existing_item
 519                    .project_entry_ids(cx)
 520                    .get(0)
 521                    .map_or(false, |existing_entry_id| {
 522                        Some(existing_entry_id) == project_entry_id.as_ref()
 523                    })
 524            } else {
 525                false
 526            }
 527        });
 528
 529        if let Some(existing_item_index) = existing_item_index {
 530            // If the item already exists, move it to the desired destination and activate it
 531            pane.update(cx, |pane, cx| {
 532                if existing_item_index != insertion_index {
 533                    cx.reparent(&item);
 534                    let existing_item_is_active = existing_item_index == pane.active_item_index;
 535
 536                    // If the caller didn't specify a destination and the added item is already
 537                    // the active one, don't move it
 538                    if existing_item_is_active && destination_index.is_none() {
 539                        insertion_index = existing_item_index;
 540                    } else {
 541                        pane.items.remove(existing_item_index);
 542                        if existing_item_index < pane.active_item_index {
 543                            pane.active_item_index -= 1;
 544                        }
 545                        insertion_index = insertion_index.min(pane.items.len());
 546
 547                        pane.items.insert(insertion_index, item.clone());
 548
 549                        if existing_item_is_active {
 550                            pane.active_item_index = insertion_index;
 551                        } else if insertion_index <= pane.active_item_index {
 552                            pane.active_item_index += 1;
 553                        }
 554                    }
 555
 556                    cx.notify();
 557                }
 558
 559                pane.activate_item(insertion_index, activate_pane, focus_item, cx);
 560            });
 561        } else {
 562            pane.update(cx, |pane, cx| {
 563                cx.reparent(&item);
 564                pane.items.insert(insertion_index, item);
 565                if insertion_index <= pane.active_item_index {
 566                    pane.active_item_index += 1;
 567                }
 568
 569                pane.activate_item(insertion_index, activate_pane, focus_item, cx);
 570                cx.notify();
 571            });
 572        }
 573    }
 574
 575    pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> {
 576        self.items.iter()
 577    }
 578
 579    pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
 580        self.items
 581            .iter()
 582            .filter_map(|item| item.to_any().downcast())
 583    }
 584
 585    pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
 586        self.items.get(self.active_item_index).cloned()
 587    }
 588
 589    pub fn item_for_entry(
 590        &self,
 591        entry_id: ProjectEntryId,
 592        cx: &AppContext,
 593    ) -> Option<Box<dyn ItemHandle>> {
 594        self.items.iter().find_map(|item| {
 595            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
 596                Some(item.boxed_clone())
 597            } else {
 598                None
 599            }
 600        })
 601    }
 602
 603    pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
 604        self.items.iter().position(|i| i.id() == item.id())
 605    }
 606
 607    pub fn activate_item(
 608        &mut self,
 609        index: usize,
 610        activate_pane: bool,
 611        focus_item: bool,
 612        cx: &mut ViewContext<Self>,
 613    ) {
 614        use NavigationMode::{GoingBack, GoingForward};
 615        if index < self.items.len() {
 616            let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
 617            if prev_active_item_ix != self.active_item_index
 618                || matches!(self.nav_history.borrow().mode, GoingBack | GoingForward)
 619            {
 620                if let Some(prev_item) = self.items.get(prev_active_item_ix) {
 621                    prev_item.deactivated(cx);
 622                }
 623                cx.emit(Event::ActivateItem {
 624                    local: activate_pane,
 625                });
 626            }
 627            self.update_toolbar(cx);
 628            if focus_item {
 629                self.focus_active_item(cx);
 630            }
 631            if activate_pane {
 632                cx.emit(Event::Focused);
 633            }
 634            self.autoscroll = true;
 635            cx.notify();
 636        }
 637    }
 638
 639    pub fn activate_prev_item(&mut self, cx: &mut ViewContext<Self>) {
 640        let mut index = self.active_item_index;
 641        if index > 0 {
 642            index -= 1;
 643        } else if !self.items.is_empty() {
 644            index = self.items.len() - 1;
 645        }
 646        self.activate_item(index, true, true, cx);
 647    }
 648
 649    pub fn activate_next_item(&mut self, cx: &mut ViewContext<Self>) {
 650        let mut index = self.active_item_index;
 651        if index + 1 < self.items.len() {
 652            index += 1;
 653        } else {
 654            index = 0;
 655        }
 656        self.activate_item(index, true, true, cx);
 657    }
 658
 659    pub fn close_active_item(
 660        workspace: &mut Workspace,
 661        _: &CloseActiveItem,
 662        cx: &mut ViewContext<Workspace>,
 663    ) -> Option<Task<Result<()>>> {
 664        let pane_handle = workspace.active_pane().clone();
 665        let pane = pane_handle.read(cx);
 666        if pane.items.is_empty() {
 667            None
 668        } else {
 669            let item_id_to_close = pane.items[pane.active_item_index].id();
 670            let task = Self::close_items(workspace, pane_handle, cx, move |item_id| {
 671                item_id == item_id_to_close
 672            });
 673            Some(cx.foreground().spawn(async move {
 674                task.await?;
 675                Ok(())
 676            }))
 677        }
 678    }
 679
 680    pub fn close_inactive_items(
 681        workspace: &mut Workspace,
 682        _: &CloseInactiveItems,
 683        cx: &mut ViewContext<Workspace>,
 684    ) -> Option<Task<Result<()>>> {
 685        let pane_handle = workspace.active_pane().clone();
 686        let pane = pane_handle.read(cx);
 687        if pane.items.is_empty() {
 688            None
 689        } else {
 690            let active_item_id = pane.items[pane.active_item_index].id();
 691            let task =
 692                Self::close_items(workspace, pane_handle, cx, move |id| id != active_item_id);
 693            Some(cx.foreground().spawn(async move {
 694                task.await?;
 695                Ok(())
 696            }))
 697        }
 698    }
 699
 700    pub fn close_item(
 701        workspace: &mut Workspace,
 702        pane: ViewHandle<Pane>,
 703        item_id_to_close: usize,
 704        cx: &mut ViewContext<Workspace>,
 705    ) -> Task<Result<bool>> {
 706        Self::close_items(workspace, pane, cx, move |view_id| {
 707            view_id == item_id_to_close
 708        })
 709    }
 710
 711    pub fn close_items(
 712        workspace: &mut Workspace,
 713        pane: ViewHandle<Pane>,
 714        cx: &mut ViewContext<Workspace>,
 715        should_close: impl 'static + Fn(usize) -> bool,
 716    ) -> Task<Result<bool>> {
 717        let project = workspace.project().clone();
 718
 719        // Find the items to close.
 720        let mut items_to_close = Vec::new();
 721        for item in &pane.read(cx).items {
 722            if should_close(item.id()) {
 723                items_to_close.push(item.boxed_clone());
 724            }
 725        }
 726
 727        // If a buffer is open both in a singleton editor and in a multibuffer, make sure
 728        // to focus the singleton buffer when prompting to save that buffer, as opposed
 729        // to focusing the multibuffer, because this gives the user a more clear idea
 730        // of what content they would be saving.
 731        items_to_close.sort_by_key(|item| !item.is_singleton(cx));
 732
 733        cx.spawn(|workspace, mut cx| async move {
 734            let mut saved_project_entry_ids = HashSet::default();
 735            for item in items_to_close.clone() {
 736                // Find the item's current index and its set of project entries. Avoid
 737                // storing these in advance, in case they have changed since this task
 738                // was started.
 739                let (item_ix, mut project_entry_ids) = pane.read_with(&cx, |pane, cx| {
 740                    (pane.index_for_item(&*item), item.project_entry_ids(cx))
 741                });
 742                let item_ix = if let Some(ix) = item_ix {
 743                    ix
 744                } else {
 745                    continue;
 746                };
 747
 748                // If an item hasn't yet been associated with a project entry, then always
 749                // prompt to save it before closing it. Otherwise, check if the item has
 750                // any project entries that are not open anywhere else in the workspace,
 751                // AND that the user has not already been prompted to save. If there are
 752                // any such project entries, prompt the user to save this item.
 753                let should_save = if project_entry_ids.is_empty() {
 754                    true
 755                } else {
 756                    workspace.read_with(&cx, |workspace, cx| {
 757                        for item in workspace.items(cx) {
 758                            if !items_to_close
 759                                .iter()
 760                                .any(|item_to_close| item_to_close.id() == item.id())
 761                            {
 762                                let other_project_entry_ids = item.project_entry_ids(cx);
 763                                project_entry_ids
 764                                    .retain(|id| !other_project_entry_ids.contains(id));
 765                            }
 766                        }
 767                    });
 768                    project_entry_ids
 769                        .iter()
 770                        .any(|id| saved_project_entry_ids.insert(*id))
 771                };
 772
 773                if should_save
 774                    && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx)
 775                        .await?
 776                {
 777                    break;
 778                }
 779
 780                // Remove the item from the pane.
 781                pane.update(&mut cx, |pane, cx| {
 782                    if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
 783                        pane.remove_item(item_ix, cx);
 784                    }
 785                });
 786            }
 787
 788            pane.update(&mut cx, |_, cx| cx.notify());
 789            Ok(true)
 790        })
 791    }
 792
 793    fn remove_item(&mut self, item_ix: usize, cx: &mut ViewContext<Self>) {
 794        if item_ix == self.active_item_index {
 795            // Activate the previous item if possible.
 796            // This returns the user to the previously opened tab if they closed
 797            // a new item they just navigated to.
 798            if item_ix > 0 {
 799                self.activate_prev_item(cx);
 800            } else if item_ix + 1 < self.items.len() {
 801                self.activate_next_item(cx);
 802            }
 803        }
 804
 805        let item = self.items.remove(item_ix);
 806        cx.emit(Event::RemoveItem { item_id: item.id() });
 807        if self.items.is_empty() {
 808            item.deactivated(cx);
 809            self.update_toolbar(cx);
 810            cx.emit(Event::Remove);
 811        }
 812
 813        if item_ix < self.active_item_index {
 814            self.active_item_index -= 1;
 815        }
 816
 817        self.nav_history
 818            .borrow_mut()
 819            .set_mode(NavigationMode::ClosingItem);
 820        item.deactivated(cx);
 821        self.nav_history
 822            .borrow_mut()
 823            .set_mode(NavigationMode::Normal);
 824
 825        if let Some(path) = item.project_path(cx) {
 826            self.nav_history
 827                .borrow_mut()
 828                .paths_by_item
 829                .insert(item.id(), path);
 830        } else {
 831            self.nav_history
 832                .borrow_mut()
 833                .paths_by_item
 834                .remove(&item.id());
 835        }
 836
 837        cx.notify();
 838    }
 839
 840    pub async fn save_item(
 841        project: ModelHandle<Project>,
 842        pane: &ViewHandle<Pane>,
 843        item_ix: usize,
 844        item: &dyn ItemHandle,
 845        should_prompt_for_save: bool,
 846        cx: &mut AsyncAppContext,
 847    ) -> Result<bool> {
 848        const CONFLICT_MESSAGE: &str =
 849            "This file has changed on disk since you started editing it. Do you want to overwrite it?";
 850        const DIRTY_MESSAGE: &str = "This file contains unsaved edits. Do you want to save it?";
 851
 852        let (has_conflict, is_dirty, can_save, is_singleton) = cx.read(|cx| {
 853            (
 854                item.has_conflict(cx),
 855                item.is_dirty(cx),
 856                item.can_save(cx),
 857                item.is_singleton(cx),
 858            )
 859        });
 860
 861        if has_conflict && can_save {
 862            let mut answer = pane.update(cx, |pane, cx| {
 863                pane.activate_item(item_ix, true, true, cx);
 864                cx.prompt(
 865                    PromptLevel::Warning,
 866                    CONFLICT_MESSAGE,
 867                    &["Overwrite", "Discard", "Cancel"],
 868                )
 869            });
 870            match answer.next().await {
 871                Some(0) => cx.update(|cx| item.save(project, cx)).await?,
 872                Some(1) => cx.update(|cx| item.reload(project, cx)).await?,
 873                _ => return Ok(false),
 874            }
 875        } else if is_dirty && (can_save || is_singleton) {
 876            let will_autosave = cx.read(|cx| {
 877                matches!(
 878                    cx.global::<Settings>().autosave,
 879                    Autosave::OnFocusChange | Autosave::OnWindowChange
 880                ) && Self::can_autosave_item(&*item, cx)
 881            });
 882            let should_save = if should_prompt_for_save && !will_autosave {
 883                let mut answer = pane.update(cx, |pane, cx| {
 884                    pane.activate_item(item_ix, true, true, cx);
 885                    cx.prompt(
 886                        PromptLevel::Warning,
 887                        DIRTY_MESSAGE,
 888                        &["Save", "Don't Save", "Cancel"],
 889                    )
 890                });
 891                match answer.next().await {
 892                    Some(0) => true,
 893                    Some(1) => false,
 894                    _ => return Ok(false),
 895                }
 896            } else {
 897                true
 898            };
 899
 900            if should_save {
 901                if can_save {
 902                    cx.update(|cx| item.save(project, cx)).await?;
 903                } else if is_singleton {
 904                    let start_abs_path = project
 905                        .read_with(cx, |project, cx| {
 906                            let worktree = project.visible_worktrees(cx).next()?;
 907                            Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
 908                        })
 909                        .unwrap_or_else(|| Path::new("").into());
 910
 911                    let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
 912                    if let Some(abs_path) = abs_path.next().await.flatten() {
 913                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
 914                    } else {
 915                        return Ok(false);
 916                    }
 917                }
 918            }
 919        }
 920        Ok(true)
 921    }
 922
 923    fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
 924        let is_deleted = item.project_entry_ids(cx).is_empty();
 925        item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
 926    }
 927
 928    pub fn autosave_item(
 929        item: &dyn ItemHandle,
 930        project: ModelHandle<Project>,
 931        cx: &mut MutableAppContext,
 932    ) -> Task<Result<()>> {
 933        if Self::can_autosave_item(item, cx) {
 934            item.save(project, cx)
 935        } else {
 936            Task::ready(Ok(()))
 937        }
 938    }
 939
 940    pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
 941        if let Some(active_item) = self.active_item() {
 942            cx.focus(active_item);
 943        }
 944    }
 945
 946    fn move_item(
 947        workspace: &mut Workspace,
 948        from: ViewHandle<Pane>,
 949        to: ViewHandle<Pane>,
 950        item_to_move: usize,
 951        destination_index: usize,
 952        cx: &mut ViewContext<Workspace>,
 953    ) {
 954        let item_to_move = from
 955            .read(cx)
 956            .items()
 957            .enumerate()
 958            .find(|(_, item_handle)| item_handle.id() == item_to_move);
 959
 960        if item_to_move.is_none() {
 961            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
 962            return;
 963        }
 964
 965        let (item_ix, item_handle) = item_to_move.unwrap();
 966        // This automatically removes duplicate items in the pane
 967        Pane::add_item(
 968            workspace,
 969            &to,
 970            item_handle.clone(),
 971            true,
 972            true,
 973            Some(destination_index),
 974            cx,
 975        );
 976
 977        if from != to {
 978            // Close item from previous pane
 979            from.update(cx, |from, cx| {
 980                from.remove_item(item_ix, cx);
 981            });
 982        }
 983
 984        cx.focus(to);
 985    }
 986
 987    pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
 988        cx.emit(Event::Split(direction));
 989    }
 990
 991    fn deploy_split_menu(&mut self, action: &DeploySplitMenu, cx: &mut ViewContext<Self>) {
 992        self.tab_bar_context_menu.update(cx, |menu, cx| {
 993            menu.show(
 994                action.position,
 995                AnchorCorner::TopRight,
 996                vec![
 997                    ContextMenuItem::item("Split Right", SplitRight),
 998                    ContextMenuItem::item("Split Left", SplitLeft),
 999                    ContextMenuItem::item("Split Up", SplitUp),
1000                    ContextMenuItem::item("Split Down", SplitDown),
1001                ],
1002                cx,
1003            );
1004        });
1005    }
1006
1007    fn deploy_dock_menu(&mut self, action: &DeployDockMenu, cx: &mut ViewContext<Self>) {
1008        self.tab_bar_context_menu.update(cx, |menu, cx| {
1009            menu.show(
1010                action.position,
1011                AnchorCorner::TopRight,
1012                vec![
1013                    ContextMenuItem::item("Move Dock Right", MoveDock(DockAnchor::Right)),
1014                    ContextMenuItem::item("Move Dock Bottom", MoveDock(DockAnchor::Bottom)),
1015                    ContextMenuItem::item("Move Dock Maximized", MoveDock(DockAnchor::Expanded)),
1016                ],
1017                cx,
1018            );
1019        });
1020    }
1021
1022    fn deploy_new_menu(&mut self, action: &DeployNewMenu, cx: &mut ViewContext<Self>) {
1023        self.tab_bar_context_menu.update(cx, |menu, cx| {
1024            menu.show(
1025                action.position,
1026                AnchorCorner::TopRight,
1027                vec![
1028                    ContextMenuItem::item("New File", NewFile),
1029                    ContextMenuItem::item("New Terminal", NewTerminal),
1030                    ContextMenuItem::item("New Search", NewSearch),
1031                ],
1032                cx,
1033            );
1034        });
1035    }
1036
1037    pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
1038        &self.toolbar
1039    }
1040
1041    fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
1042        let active_item = self
1043            .items
1044            .get(self.active_item_index)
1045            .map(|item| item.as_ref());
1046        self.toolbar.update(cx, |toolbar, cx| {
1047            toolbar.set_active_pane_item(active_item, cx);
1048        });
1049    }
1050
1051    fn render_tabs(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
1052        let theme = cx.global::<Settings>().theme.clone();
1053        let filler_index = self.items.len();
1054
1055        enum Tabs {}
1056        enum Tab {}
1057        enum Filler {}
1058        let pane = cx.handle();
1059        MouseEventHandler::<Tabs>::new(0, cx, |_, cx| {
1060            let autoscroll = if mem::take(&mut self.autoscroll) {
1061                Some(self.active_item_index)
1062            } else {
1063                None
1064            };
1065
1066            let pane_active = self.is_active;
1067
1068            let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
1069            for (ix, (item, detail)) in self
1070                .items
1071                .iter()
1072                .cloned()
1073                .zip(self.tab_details(cx))
1074                .enumerate()
1075            {
1076                let detail = if detail == 0 { None } else { Some(detail) };
1077                let tab_active = ix == self.active_item_index;
1078
1079                row.add_child({
1080                    MouseEventHandler::<Tab>::new(ix, cx, {
1081                        let item = item.clone();
1082                        let pane = pane.clone();
1083                        let detail = detail.clone();
1084
1085                        let theme = cx.global::<Settings>().theme.clone();
1086
1087                        move |mouse_state, cx| {
1088                            let tab_style =
1089                                theme.workspace.tab_bar.tab_style(pane_active, tab_active);
1090                            let hovered = mouse_state.hovered;
1091                            Self::render_tab(
1092                                &item,
1093                                pane,
1094                                detail,
1095                                hovered,
1096                                Self::tab_overlay_color(hovered, theme.as_ref(), cx),
1097                                tab_style,
1098                                cx,
1099                            )
1100                        }
1101                    })
1102                    .with_cursor_style(if pane_active && tab_active {
1103                        CursorStyle::Arrow
1104                    } else {
1105                        CursorStyle::PointingHand
1106                    })
1107                    .on_down(MouseButton::Left, move |_, cx| {
1108                        cx.dispatch_action(ActivateItem(ix));
1109                    })
1110                    .on_click(MouseButton::Middle, {
1111                        let item = item.clone();
1112                        let pane = pane.clone();
1113                        move |_, cx: &mut EventContext| {
1114                            cx.dispatch_action(CloseItem {
1115                                item_id: item.id(),
1116                                pane: pane.clone(),
1117                            })
1118                        }
1119                    })
1120                    .on_up(MouseButton::Left, {
1121                        let pane = pane.clone();
1122                        move |_, cx: &mut EventContext| Pane::handle_dropped_item(&pane, ix, cx)
1123                    })
1124                    .as_draggable(
1125                        DraggedItem {
1126                            item,
1127                            pane: pane.clone(),
1128                        },
1129                        {
1130                            let theme = cx.global::<Settings>().theme.clone();
1131
1132                            let detail = detail.clone();
1133                            move |dragged_item, cx: &mut RenderContext<Workspace>| {
1134                                let tab_style = &theme.workspace.tab_bar.dragged_tab;
1135                                Self::render_tab(
1136                                    &dragged_item.item,
1137                                    dragged_item.pane.clone(),
1138                                    detail,
1139                                    false,
1140                                    None,
1141                                    &tab_style,
1142                                    cx,
1143                                )
1144                            }
1145                        },
1146                    )
1147                    .boxed()
1148                })
1149            }
1150
1151            // Use the inactive tab style along with the current pane's active status to decide how to render
1152            // the filler
1153            let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
1154            row.add_child(
1155                MouseEventHandler::<Filler>::new(0, cx, |mouse_state, cx| {
1156                    let mut filler = Empty::new()
1157                        .contained()
1158                        .with_style(filler_style.container)
1159                        .with_border(filler_style.container.border);
1160
1161                    if let Some(overlay) = Self::tab_overlay_color(mouse_state.hovered, &theme, cx)
1162                    {
1163                        filler = filler.with_overlay_color(overlay);
1164                    }
1165
1166                    filler.boxed()
1167                })
1168                .flex(1., true)
1169                .named("filler"),
1170            );
1171
1172            row.boxed()
1173        })
1174        .on_up(MouseButton::Left, move |_, cx| {
1175            Pane::handle_dropped_item(&pane, filler_index, cx)
1176        })
1177    }
1178
1179    fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
1180        let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
1181
1182        let mut tab_descriptions = HashMap::default();
1183        let mut done = false;
1184        while !done {
1185            done = true;
1186
1187            // Store item indices by their tab description.
1188            for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
1189                if let Some(description) = item.tab_description(*detail, cx) {
1190                    if *detail == 0
1191                        || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
1192                    {
1193                        tab_descriptions
1194                            .entry(description)
1195                            .or_insert(Vec::new())
1196                            .push(ix);
1197                    }
1198                }
1199            }
1200
1201            // If two or more items have the same tab description, increase their level
1202            // of detail and try again.
1203            for (_, item_ixs) in tab_descriptions.drain() {
1204                if item_ixs.len() > 1 {
1205                    done = false;
1206                    for ix in item_ixs {
1207                        tab_details[ix] += 1;
1208                    }
1209                }
1210            }
1211        }
1212
1213        tab_details
1214    }
1215
1216    fn render_tab<V: View>(
1217        item: &Box<dyn ItemHandle>,
1218        pane: WeakViewHandle<Pane>,
1219        detail: Option<usize>,
1220        hovered: bool,
1221        overlay: Option<Color>,
1222        tab_style: &theme::Tab,
1223        cx: &mut RenderContext<V>,
1224    ) -> ElementBox {
1225        let title = item.tab_content(detail, &tab_style, cx);
1226
1227        let mut tab = Flex::row()
1228            .with_child(
1229                Align::new({
1230                    let diameter = 7.0;
1231                    let icon_color = if item.has_conflict(cx) {
1232                        Some(tab_style.icon_conflict)
1233                    } else if item.is_dirty(cx) {
1234                        Some(tab_style.icon_dirty)
1235                    } else {
1236                        None
1237                    };
1238
1239                    ConstrainedBox::new(
1240                        Canvas::new(move |bounds, _, cx| {
1241                            if let Some(color) = icon_color {
1242                                let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
1243                                cx.scene.push_quad(Quad {
1244                                    bounds: square,
1245                                    background: Some(color),
1246                                    border: Default::default(),
1247                                    corner_radius: diameter / 2.,
1248                                });
1249                            }
1250                        })
1251                        .boxed(),
1252                    )
1253                    .with_width(diameter)
1254                    .with_height(diameter)
1255                    .boxed()
1256                })
1257                .boxed(),
1258            )
1259            .with_child(
1260                Container::new(Align::new(title).boxed())
1261                    .with_style(ContainerStyle {
1262                        margin: Margin {
1263                            left: tab_style.spacing,
1264                            right: tab_style.spacing,
1265                            ..Default::default()
1266                        },
1267                        ..Default::default()
1268                    })
1269                    .boxed(),
1270            )
1271            .with_child(
1272                Align::new(
1273                    ConstrainedBox::new(if hovered {
1274                        let item_id = item.id();
1275                        enum TabCloseButton {}
1276                        let icon = Svg::new("icons/x_mark_thin_8.svg");
1277                        MouseEventHandler::<TabCloseButton>::new(item_id, cx, |mouse_state, _| {
1278                            if mouse_state.hovered {
1279                                icon.with_color(tab_style.icon_close_active).boxed()
1280                            } else {
1281                                icon.with_color(tab_style.icon_close).boxed()
1282                            }
1283                        })
1284                        .with_padding(Padding::uniform(4.))
1285                        .with_cursor_style(CursorStyle::PointingHand)
1286                        .on_click(MouseButton::Left, {
1287                            let pane = pane.clone();
1288                            move |_, cx| {
1289                                cx.dispatch_action(CloseItem {
1290                                    item_id,
1291                                    pane: pane.clone(),
1292                                })
1293                            }
1294                        })
1295                        .on_click(MouseButton::Middle, |_, cx| cx.propogate_event())
1296                        .named("close-tab-icon")
1297                    } else {
1298                        Empty::new().boxed()
1299                    })
1300                    .with_width(tab_style.icon_width)
1301                    .boxed(),
1302                )
1303                .boxed(),
1304            )
1305            .contained()
1306            .with_style(tab_style.container);
1307
1308        if let Some(overlay) = overlay {
1309            tab = tab.with_overlay_color(overlay);
1310        }
1311
1312        tab.constrained().with_height(tab_style.height).boxed()
1313    }
1314
1315    fn handle_dropped_item(pane: &WeakViewHandle<Pane>, index: usize, cx: &mut EventContext) {
1316        if let Some((_, dragged_item)) = cx
1317            .global::<DragAndDrop<Workspace>>()
1318            .currently_dragged::<DraggedItem>(cx.window_id)
1319        {
1320            cx.dispatch_action(MoveItem {
1321                item_id: dragged_item.item.id(),
1322                from: dragged_item.pane.clone(),
1323                to: pane.clone(),
1324                destination_index: index,
1325            })
1326        } else {
1327            cx.propogate_event();
1328        }
1329    }
1330
1331    fn tab_overlay_color(
1332        hovered: bool,
1333        theme: &Theme,
1334        cx: &mut RenderContext<Self>,
1335    ) -> Option<Color> {
1336        if hovered
1337            && cx
1338                .global::<DragAndDrop<Workspace>>()
1339                .currently_dragged::<DraggedItem>(cx.window_id())
1340                .is_some()
1341        {
1342            Some(theme.workspace.tab_bar.drop_target_overlay_color)
1343        } else {
1344            None
1345        }
1346    }
1347}
1348
1349impl Entity for Pane {
1350    type Event = Event;
1351}
1352
1353impl View for Pane {
1354    fn ui_name() -> &'static str {
1355        "Pane"
1356    }
1357
1358    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
1359        let this = cx.handle();
1360
1361        enum MouseNavigationHandler {}
1362
1363        Stack::new()
1364            .with_child(
1365                MouseEventHandler::<MouseNavigationHandler>::new(0, cx, |_, cx| {
1366                    if let Some(active_item) = self.active_item() {
1367                        Flex::column()
1368                            .with_child({
1369                                let mut tab_row = Flex::row()
1370                                    .with_child(self.render_tabs(cx).flex(1., true).named("tabs"));
1371
1372                                // Render pane buttons
1373                                let theme = cx.global::<Settings>().theme.clone();
1374                                if self.is_active {
1375                                    tab_row.add_child(
1376                                        Flex::row()
1377                                            // New menu
1378                                            .with_child(tab_bar_button(
1379                                                0,
1380                                                "icons/plus_12.svg",
1381                                                cx,
1382                                                |position| DeployNewMenu { position },
1383                                            ))
1384                                            .with_child(
1385                                                self.docked
1386                                                    .map(|anchor| {
1387                                                        // Add the dock menu button if this pane is a dock
1388                                                        let dock_icon =
1389                                                            icon_for_dock_anchor(anchor);
1390
1391                                                        tab_bar_button(
1392                                                            2,
1393                                                            dock_icon,
1394                                                            cx,
1395                                                            |position| DeployDockMenu { position },
1396                                                        )
1397                                                    })
1398                                                    .unwrap_or_else(|| {
1399                                                        // Add the split menu if this pane is not a dock
1400                                                        tab_bar_button(
1401                                                            1,
1402                                                            "icons/split_12.svg",
1403                                                            cx,
1404                                                            |position| DeployNewMenu { position },
1405                                                        )
1406                                                    }),
1407                                            )
1408                                            // Add the close dock button if this pane is a dock
1409                                            .with_children(self.docked.map(|_| {
1410                                                tab_bar_button(3, "icons/x_mark_12.svg", cx, |_| {
1411                                                    ToggleDock
1412                                                })
1413                                            }))
1414                                            .contained()
1415                                            .with_style(theme.workspace.tab_bar.container)
1416                                            .boxed(),
1417                                    )
1418                                }
1419
1420                                tab_row
1421                                    .constrained()
1422                                    .with_height(theme.workspace.tab_bar.height)
1423                                    .contained()
1424                                    .with_style(theme.workspace.tab_bar.container)
1425                                    .named("tab bar")
1426                            })
1427                            .with_child(ChildView::new(&self.toolbar).boxed())
1428                            .with_child(ChildView::new(active_item).flex(1., true).boxed())
1429                            .boxed()
1430                    } else {
1431                        enum EmptyPane {}
1432                        let theme = cx.global::<Settings>().theme.clone();
1433
1434                        MouseEventHandler::<EmptyPane>::new(0, cx, |_, _| {
1435                            Empty::new()
1436                                .contained()
1437                                .with_background_color(theme.workspace.background)
1438                                .boxed()
1439                        })
1440                        .on_down(MouseButton::Left, |_, cx| {
1441                            cx.focus_parent_view();
1442                        })
1443                        .on_up(MouseButton::Left, {
1444                            let pane = this.clone();
1445                            move |_, cx: &mut EventContext| Pane::handle_dropped_item(&pane, 0, cx)
1446                        })
1447                        .boxed()
1448                    }
1449                })
1450                .on_down(MouseButton::Navigate(NavigationDirection::Back), {
1451                    let this = this.clone();
1452                    move |_, cx| {
1453                        cx.dispatch_action(GoBack {
1454                            pane: Some(this.clone()),
1455                        });
1456                    }
1457                })
1458                .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
1459                    let this = this.clone();
1460                    move |_, cx| {
1461                        cx.dispatch_action(GoForward {
1462                            pane: Some(this.clone()),
1463                        })
1464                    }
1465                })
1466                .boxed(),
1467            )
1468            .with_child(ChildView::new(&self.tab_bar_context_menu).boxed())
1469            .named("pane")
1470    }
1471
1472    fn on_focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
1473        if cx.is_self_focused() {
1474            if let Some(last_focused_view) = self
1475                .last_focused_view
1476                .as_ref()
1477                .and_then(|handle| handle.upgrade(cx))
1478            {
1479                cx.focus(last_focused_view);
1480            } else {
1481                self.focus_active_item(cx);
1482            }
1483        } else {
1484            self.last_focused_view = Some(focused.downgrade());
1485        }
1486        cx.emit(Event::Focused);
1487    }
1488}
1489
1490fn tab_bar_button<A: Action>(
1491    index: usize,
1492    icon: &'static str,
1493    cx: &mut RenderContext<Pane>,
1494    action_builder: impl 'static + Fn(Vector2F) -> A,
1495) -> ElementBox {
1496    enum TabBarButton {}
1497
1498    MouseEventHandler::<TabBarButton>::new(index, cx, |mouse_state, cx| {
1499        let theme = &cx.global::<Settings>().theme.workspace.tab_bar;
1500        let style = theme.pane_button.style_for(mouse_state, false);
1501        Svg::new(icon)
1502            .with_color(style.color)
1503            .constrained()
1504            .with_width(style.icon_width)
1505            .aligned()
1506            .constrained()
1507            .with_width(style.button_width)
1508            .with_height(style.button_width)
1509            .aligned()
1510            .boxed()
1511    })
1512    .with_cursor_style(CursorStyle::PointingHand)
1513    .on_click(MouseButton::Left, move |e, cx| {
1514        cx.dispatch_action(action_builder(e.region.lower_right()));
1515    })
1516    .boxed()
1517}
1518
1519impl ItemNavHistory {
1520    pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut MutableAppContext) {
1521        self.history.borrow_mut().push(data, self.item.clone(), cx);
1522    }
1523
1524    pub fn pop_backward(&self, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
1525        self.history.borrow_mut().pop(NavigationMode::GoingBack, cx)
1526    }
1527
1528    pub fn pop_forward(&self, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
1529        self.history
1530            .borrow_mut()
1531            .pop(NavigationMode::GoingForward, cx)
1532    }
1533}
1534
1535impl NavHistory {
1536    fn set_mode(&mut self, mode: NavigationMode) {
1537        self.mode = mode;
1538    }
1539
1540    fn disable(&mut self) {
1541        self.mode = NavigationMode::Disabled;
1542    }
1543
1544    fn enable(&mut self) {
1545        self.mode = NavigationMode::Normal;
1546    }
1547
1548    fn pop(&mut self, mode: NavigationMode, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
1549        let entry = match mode {
1550            NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
1551                return None
1552            }
1553            NavigationMode::GoingBack => &mut self.backward_stack,
1554            NavigationMode::GoingForward => &mut self.forward_stack,
1555            NavigationMode::ReopeningClosedItem => &mut self.closed_stack,
1556        }
1557        .pop_back();
1558        if entry.is_some() {
1559            self.did_update(cx);
1560        }
1561        entry
1562    }
1563
1564    fn push<D: 'static + Any>(
1565        &mut self,
1566        data: Option<D>,
1567        item: Rc<dyn WeakItemHandle>,
1568        cx: &mut MutableAppContext,
1569    ) {
1570        match self.mode {
1571            NavigationMode::Disabled => {}
1572            NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
1573                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1574                    self.backward_stack.pop_front();
1575                }
1576                self.backward_stack.push_back(NavigationEntry {
1577                    item,
1578                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1579                });
1580                self.forward_stack.clear();
1581            }
1582            NavigationMode::GoingBack => {
1583                if self.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1584                    self.forward_stack.pop_front();
1585                }
1586                self.forward_stack.push_back(NavigationEntry {
1587                    item,
1588                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1589                });
1590            }
1591            NavigationMode::GoingForward => {
1592                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1593                    self.backward_stack.pop_front();
1594                }
1595                self.backward_stack.push_back(NavigationEntry {
1596                    item,
1597                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1598                });
1599            }
1600            NavigationMode::ClosingItem => {
1601                if self.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1602                    self.closed_stack.pop_front();
1603                }
1604                self.closed_stack.push_back(NavigationEntry {
1605                    item,
1606                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1607                });
1608            }
1609        }
1610        self.did_update(cx);
1611    }
1612
1613    fn did_update(&self, cx: &mut MutableAppContext) {
1614        if let Some(pane) = self.pane.upgrade(cx) {
1615            cx.defer(move |cx| pane.update(cx, |pane, cx| pane.history_updated(cx)));
1616        }
1617    }
1618}
1619
1620#[cfg(test)]
1621mod tests {
1622    use super::*;
1623    use crate::tests::TestItem;
1624    use gpui::TestAppContext;
1625    use project::FakeFs;
1626
1627    #[gpui::test]
1628    async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
1629        cx.foreground().forbid_parking();
1630        Settings::test_async(cx);
1631        let fs = FakeFs::new(cx.background());
1632
1633        let project = Project::test(fs, None, cx).await;
1634        let (_, workspace) =
1635            cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx));
1636        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1637
1638        // 1. Add with a destination index
1639        //   a. Add before the active item
1640        set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
1641        workspace.update(cx, |workspace, cx| {
1642            Pane::add_item(
1643                workspace,
1644                &pane,
1645                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1646                false,
1647                false,
1648                Some(0),
1649                cx,
1650            );
1651        });
1652        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
1653
1654        //   b. Add after the active item
1655        set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
1656        workspace.update(cx, |workspace, cx| {
1657            Pane::add_item(
1658                workspace,
1659                &pane,
1660                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1661                false,
1662                false,
1663                Some(2),
1664                cx,
1665            );
1666        });
1667        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
1668
1669        //   c. Add at the end of the item list (including off the length)
1670        set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
1671        workspace.update(cx, |workspace, cx| {
1672            Pane::add_item(
1673                workspace,
1674                &pane,
1675                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1676                false,
1677                false,
1678                Some(5),
1679                cx,
1680            );
1681        });
1682        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
1683
1684        // 2. Add without a destination index
1685        //   a. Add with active item at the start of the item list
1686        set_labeled_items(&workspace, &pane, ["A*", "B", "C"], cx);
1687        workspace.update(cx, |workspace, cx| {
1688            Pane::add_item(
1689                workspace,
1690                &pane,
1691                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1692                false,
1693                false,
1694                None,
1695                cx,
1696            );
1697        });
1698        set_labeled_items(&workspace, &pane, ["A", "D*", "B", "C"], cx);
1699
1700        //   b. Add with active item at the end of the item list
1701        set_labeled_items(&workspace, &pane, ["A", "B", "C*"], cx);
1702        workspace.update(cx, |workspace, cx| {
1703            Pane::add_item(
1704                workspace,
1705                &pane,
1706                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
1707                false,
1708                false,
1709                None,
1710                cx,
1711            );
1712        });
1713        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
1714    }
1715
1716    #[gpui::test]
1717    async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
1718        cx.foreground().forbid_parking();
1719        Settings::test_async(cx);
1720        let fs = FakeFs::new(cx.background());
1721
1722        let project = Project::test(fs, None, cx).await;
1723        let (_, workspace) =
1724            cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx));
1725        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1726
1727        // 1. Add with a destination index
1728        //   1a. Add before the active item
1729        let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx);
1730        workspace.update(cx, |workspace, cx| {
1731            Pane::add_item(workspace, &pane, d, false, false, Some(0), cx);
1732        });
1733        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
1734
1735        //   1b. Add after the active item
1736        let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx);
1737        workspace.update(cx, |workspace, cx| {
1738            Pane::add_item(workspace, &pane, d, false, false, Some(2), cx);
1739        });
1740        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
1741
1742        //   1c. Add at the end of the item list (including off the length)
1743        let [a, _, _, _] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx);
1744        workspace.update(cx, |workspace, cx| {
1745            Pane::add_item(workspace, &pane, a, false, false, Some(5), cx);
1746        });
1747        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
1748
1749        //   1d. Add same item to active index
1750        let [_, b, _] = set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
1751        workspace.update(cx, |workspace, cx| {
1752            Pane::add_item(workspace, &pane, b, false, false, Some(1), cx);
1753        });
1754        assert_item_labels(&pane, ["A", "B*", "C"], cx);
1755
1756        //   1e. Add item to index after same item in last position
1757        let [_, _, c] = set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx);
1758        workspace.update(cx, |workspace, cx| {
1759            Pane::add_item(workspace, &pane, c, false, false, Some(2), cx);
1760        });
1761        assert_item_labels(&pane, ["A", "B", "C*"], cx);
1762
1763        // 2. Add without a destination index
1764        //   2a. Add with active item at the start of the item list
1765        let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A*", "B", "C", "D"], cx);
1766        workspace.update(cx, |workspace, cx| {
1767            Pane::add_item(workspace, &pane, d, false, false, None, cx);
1768        });
1769        assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
1770
1771        //   2b. Add with active item at the end of the item list
1772        let [a, _, _, _] = set_labeled_items(&workspace, &pane, ["A", "B", "C", "D*"], cx);
1773        workspace.update(cx, |workspace, cx| {
1774            Pane::add_item(workspace, &pane, a, false, false, None, cx);
1775        });
1776        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
1777
1778        //   2c. Add active item to active item at end of list
1779        let [_, _, c] = set_labeled_items(&workspace, &pane, ["A", "B", "C*"], cx);
1780        workspace.update(cx, |workspace, cx| {
1781            Pane::add_item(workspace, &pane, c, false, false, None, cx);
1782        });
1783        assert_item_labels(&pane, ["A", "B", "C*"], cx);
1784
1785        //   2d. Add active item to active item at start of list
1786        let [a, _, _] = set_labeled_items(&workspace, &pane, ["A*", "B", "C"], cx);
1787        workspace.update(cx, |workspace, cx| {
1788            Pane::add_item(workspace, &pane, a, false, false, None, cx);
1789        });
1790        assert_item_labels(&pane, ["A*", "B", "C"], cx);
1791    }
1792
1793    #[gpui::test]
1794    async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
1795        cx.foreground().forbid_parking();
1796        Settings::test_async(cx);
1797        let fs = FakeFs::new(cx.background());
1798
1799        let project = Project::test(fs, None, cx).await;
1800        let (_, workspace) =
1801            cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx));
1802        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1803
1804        // singleton view
1805        workspace.update(cx, |workspace, cx| {
1806            let item = TestItem::new()
1807                .with_singleton(true)
1808                .with_label("buffer 1")
1809                .with_project_entry_ids(&[1]);
1810
1811            Pane::add_item(
1812                workspace,
1813                &pane,
1814                Box::new(cx.add_view(|_| item)),
1815                false,
1816                false,
1817                None,
1818                cx,
1819            );
1820        });
1821        assert_item_labels(&pane, ["buffer 1*"], cx);
1822
1823        // new singleton view with the same project entry
1824        workspace.update(cx, |workspace, cx| {
1825            let item = TestItem::new()
1826                .with_singleton(true)
1827                .with_label("buffer 1")
1828                .with_project_entry_ids(&[1]);
1829
1830            Pane::add_item(
1831                workspace,
1832                &pane,
1833                Box::new(cx.add_view(|_| item)),
1834                false,
1835                false,
1836                None,
1837                cx,
1838            );
1839        });
1840        assert_item_labels(&pane, ["buffer 1*"], cx);
1841
1842        // new singleton view with different project entry
1843        workspace.update(cx, |workspace, cx| {
1844            let item = TestItem::new()
1845                .with_singleton(true)
1846                .with_label("buffer 2")
1847                .with_project_entry_ids(&[2]);
1848
1849            Pane::add_item(
1850                workspace,
1851                &pane,
1852                Box::new(cx.add_view(|_| item)),
1853                false,
1854                false,
1855                None,
1856                cx,
1857            );
1858        });
1859        assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
1860
1861        // new multibuffer view with the same project entry
1862        workspace.update(cx, |workspace, cx| {
1863            let item = TestItem::new()
1864                .with_singleton(false)
1865                .with_label("multibuffer 1")
1866                .with_project_entry_ids(&[1]);
1867
1868            Pane::add_item(
1869                workspace,
1870                &pane,
1871                Box::new(cx.add_view(|_| item)),
1872                false,
1873                false,
1874                None,
1875                cx,
1876            );
1877        });
1878        assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
1879
1880        // another multibuffer view with the same project entry
1881        workspace.update(cx, |workspace, cx| {
1882            let item = TestItem::new()
1883                .with_singleton(false)
1884                .with_label("multibuffer 1b")
1885                .with_project_entry_ids(&[1]);
1886
1887            Pane::add_item(
1888                workspace,
1889                &pane,
1890                Box::new(cx.add_view(|_| item)),
1891                false,
1892                false,
1893                None,
1894                cx,
1895            );
1896        });
1897        assert_item_labels(
1898            &pane,
1899            ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
1900            cx,
1901        );
1902    }
1903
1904    fn set_labeled_items<const COUNT: usize>(
1905        workspace: &ViewHandle<Workspace>,
1906        pane: &ViewHandle<Pane>,
1907        labels: [&str; COUNT],
1908        cx: &mut TestAppContext,
1909    ) -> [Box<ViewHandle<TestItem>>; COUNT] {
1910        pane.update(cx, |pane, _| {
1911            pane.items.clear();
1912        });
1913
1914        workspace.update(cx, |workspace, cx| {
1915            let mut active_item_index = 0;
1916
1917            let mut index = 0;
1918            let items = labels.map(|mut label| {
1919                if label.ends_with("*") {
1920                    label = label.trim_end_matches("*");
1921                    active_item_index = index;
1922                }
1923
1924                let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
1925                Pane::add_item(
1926                    workspace,
1927                    pane,
1928                    labeled_item.clone(),
1929                    false,
1930                    false,
1931                    None,
1932                    cx,
1933                );
1934                index += 1;
1935                labeled_item
1936            });
1937
1938            pane.update(cx, |pane, cx| {
1939                pane.activate_item(active_item_index, false, false, cx)
1940            });
1941
1942            items
1943        })
1944    }
1945
1946    // Assert the item label, with the active item label suffixed with a '*'
1947    fn assert_item_labels<const COUNT: usize>(
1948        pane: &ViewHandle<Pane>,
1949        expected_states: [&str; COUNT],
1950        cx: &mut TestAppContext,
1951    ) {
1952        pane.read_with(cx, |pane, cx| {
1953            let actual_states = pane
1954                .items
1955                .iter()
1956                .enumerate()
1957                .map(|(ix, item)| {
1958                    let mut state = item
1959                        .to_any()
1960                        .downcast::<TestItem>()
1961                        .unwrap()
1962                        .read(cx)
1963                        .label
1964                        .clone();
1965                    if ix == pane.active_item_index {
1966                        state.push('*');
1967                    }
1968                    state
1969                })
1970                .collect::<Vec<_>>();
1971
1972            assert_eq!(
1973                actual_states, expected_states,
1974                "pane items do not match expectation"
1975            );
1976        })
1977    }
1978}