pane.rs

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