pane.rs

   1use super::{ItemHandle, SplitDirection};
   2use crate::{toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace};
   3use anyhow::Result;
   4use collections::{HashMap, HashSet, VecDeque};
   5use context_menu::{ContextMenu, ContextMenuItem};
   6use drag_and_drop::{DragAndDrop, Draggable};
   7use futures::StreamExt;
   8use gpui::{
   9    actions,
  10    elements::*,
  11    geometry::{
  12        rect::RectF,
  13        vector::{vec2f, Vector2F},
  14    },
  15    impl_actions, impl_internal_actions,
  16    platform::{CursorStyle, NavigationDirection},
  17    AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
  18    ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
  19    ViewContext, ViewHandle, WeakViewHandle,
  20};
  21use project::{Project, ProjectEntryId, ProjectPath};
  22use serde::Deserialize;
  23use settings::{Autosave, Settings};
  24use std::{any::Any, cell::RefCell, mem, path::Path, rc::Rc};
  25use util::ResultExt;
  26
  27#[derive(Clone, Deserialize, PartialEq)]
  28pub struct ActivateItem(pub usize);
  29
  30actions!(
  31    pane,
  32    [
  33        ActivatePrevItem,
  34        ActivateNextItem,
  35        ActivateLastItem,
  36        CloseActiveItem,
  37        CloseInactiveItems,
  38        ReopenClosedItem,
  39        SplitLeft,
  40        SplitUp,
  41        SplitRight,
  42        SplitDown,
  43    ]
  44);
  45
  46#[derive(Clone, PartialEq)]
  47pub struct CloseItem {
  48    pub item_id: usize,
  49    pub pane: WeakViewHandle<Pane>,
  50}
  51
  52#[derive(Clone, PartialEq)]
  53pub struct MoveItem {
  54    pub item_id: usize,
  55    pub from: WeakViewHandle<Pane>,
  56    pub to: WeakViewHandle<Pane>,
  57    pub destination_index: usize,
  58}
  59
  60#[derive(Clone, Deserialize, PartialEq)]
  61pub struct GoBack {
  62    #[serde(skip_deserializing)]
  63    pub pane: Option<WeakViewHandle<Pane>>,
  64}
  65
  66#[derive(Clone, Deserialize, PartialEq)]
  67pub struct GoForward {
  68    #[serde(skip_deserializing)]
  69    pub pane: Option<WeakViewHandle<Pane>>,
  70}
  71
  72#[derive(Clone, PartialEq)]
  73pub struct DeploySplitMenu {
  74    position: Vector2F,
  75}
  76
  77#[derive(Clone, PartialEq)]
  78pub struct DeployNewMenu {
  79    position: Vector2F,
  80}
  81
  82impl_actions!(pane, [GoBack, GoForward, ActivateItem]);
  83impl_internal_actions!(pane, [CloseItem, DeploySplitMenu, DeployNewMenu, MoveItem]);
  84
  85const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
  86
  87pub fn init(cx: &mut MutableAppContext) {
  88    cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
  89        pane.activate_item(action.0, true, true, false, cx);
  90    });
  91    cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
  92        pane.activate_item(pane.items.len() - 1, true, true, false, cx);
  93    });
  94    cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
  95        pane.activate_prev_item(cx);
  96    });
  97    cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
  98        pane.activate_next_item(cx);
  99    });
 100    cx.add_async_action(Pane::close_active_item);
 101    cx.add_async_action(Pane::close_inactive_items);
 102    cx.add_async_action(|workspace: &mut Workspace, action: &CloseItem, cx| {
 103        let pane = action.pane.upgrade(cx)?;
 104        let task = Pane::close_item(workspace, pane, action.item_id, cx);
 105        Some(cx.foreground().spawn(async move {
 106            task.await?;
 107            Ok(())
 108        }))
 109    });
 110    cx.add_action(|workspace: &mut Workspace, action: &MoveItem, cx| {
 111        // Get item handle to move
 112        let from = if let Some(from) = action.from.upgrade(cx) {
 113            from
 114        } else {
 115            return;
 116        };
 117
 118        let (item_ix, item_handle) = from
 119            .read(cx)
 120            .items()
 121            .enumerate()
 122            .find(|(_, item_handle)| item_handle.id() == action.item_id)
 123            .expect("Tried to move item handle which was not in from pane");
 124
 125        // Add item to new pane at given index
 126        let to = if let Some(to) = action.to.upgrade(cx) {
 127            to
 128        } else {
 129            return;
 130        };
 131
 132        // This automatically removes duplicate items in the pane
 133        Pane::add_item_at(
 134            workspace,
 135            to,
 136            item_handle.clone(),
 137            true,
 138            true,
 139            Some(action.destination_index),
 140            cx,
 141        );
 142
 143        if action.from != action.to {
 144            // Close item from previous pane
 145            from.update(cx, |from, cx| {
 146                from.remove_item(item_ix, cx);
 147                dbg!(from.items().collect::<Vec<_>>());
 148            });
 149        }
 150    });
 151    cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
 152    cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
 153    cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
 154    cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
 155    cx.add_action(Pane::deploy_split_menu);
 156    cx.add_action(Pane::deploy_new_menu);
 157    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
 158        Pane::reopen_closed_item(workspace, cx).detach();
 159    });
 160    cx.add_action(|workspace: &mut Workspace, action: &GoBack, cx| {
 161        Pane::go_back(
 162            workspace,
 163            action
 164                .pane
 165                .as_ref()
 166                .and_then(|weak_handle| weak_handle.upgrade(cx)),
 167            cx,
 168        )
 169        .detach();
 170    });
 171    cx.add_action(|workspace: &mut Workspace, action: &GoForward, cx| {
 172        Pane::go_forward(
 173            workspace,
 174            action
 175                .pane
 176                .as_ref()
 177                .and_then(|weak_handle| weak_handle.upgrade(cx)),
 178            cx,
 179        )
 180        .detach();
 181    });
 182}
 183
 184pub enum Event {
 185    Focused,
 186    ActivateItem { local: bool },
 187    Remove,
 188    RemoveItem,
 189    Split(SplitDirection),
 190    ChangeItemTitle,
 191}
 192
 193pub struct Pane {
 194    items: Vec<Box<dyn ItemHandle>>,
 195    is_active: bool,
 196    active_item_index: usize,
 197    last_focused_view: Option<AnyWeakViewHandle>,
 198    autoscroll: bool,
 199    nav_history: Rc<RefCell<NavHistory>>,
 200    toolbar: ViewHandle<Toolbar>,
 201    context_menu: ViewHandle<ContextMenu>,
 202}
 203
 204pub struct ItemNavHistory {
 205    history: Rc<RefCell<NavHistory>>,
 206    item: Rc<dyn WeakItemHandle>,
 207}
 208
 209struct NavHistory {
 210    mode: NavigationMode,
 211    backward_stack: VecDeque<NavigationEntry>,
 212    forward_stack: VecDeque<NavigationEntry>,
 213    closed_stack: VecDeque<NavigationEntry>,
 214    paths_by_item: HashMap<usize, ProjectPath>,
 215    pane: WeakViewHandle<Pane>,
 216}
 217
 218#[derive(Copy, Clone)]
 219enum NavigationMode {
 220    Normal,
 221    GoingBack,
 222    GoingForward,
 223    ClosingItem,
 224    ReopeningClosedItem,
 225    Disabled,
 226}
 227
 228impl Default for NavigationMode {
 229    fn default() -> Self {
 230        Self::Normal
 231    }
 232}
 233
 234pub struct NavigationEntry {
 235    pub item: Rc<dyn WeakItemHandle>,
 236    pub data: Option<Box<dyn Any>>,
 237}
 238
 239impl Pane {
 240    pub fn new(cx: &mut ViewContext<Self>) -> Self {
 241        let handle = cx.weak_handle();
 242        let context_menu = cx.add_view(ContextMenu::new);
 243        Self {
 244            items: Vec::new(),
 245            is_active: true,
 246            active_item_index: 0,
 247            last_focused_view: None,
 248            autoscroll: false,
 249            nav_history: Rc::new(RefCell::new(NavHistory {
 250                mode: NavigationMode::Normal,
 251                backward_stack: Default::default(),
 252                forward_stack: Default::default(),
 253                closed_stack: Default::default(),
 254                paths_by_item: Default::default(),
 255                pane: handle.clone(),
 256            })),
 257            toolbar: cx.add_view(|_| Toolbar::new(handle)),
 258            context_menu,
 259        }
 260    }
 261
 262    pub fn set_active(&mut self, is_active: bool, cx: &mut ViewContext<Self>) {
 263        self.is_active = is_active;
 264        cx.notify();
 265    }
 266
 267    pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
 268        ItemNavHistory {
 269            history: self.nav_history.clone(),
 270            item: Rc::new(item.downgrade()),
 271        }
 272    }
 273
 274    pub fn go_back(
 275        workspace: &mut Workspace,
 276        pane: Option<ViewHandle<Pane>>,
 277        cx: &mut ViewContext<Workspace>,
 278    ) -> Task<()> {
 279        Self::navigate_history(
 280            workspace,
 281            pane.unwrap_or_else(|| workspace.active_pane().clone()),
 282            NavigationMode::GoingBack,
 283            cx,
 284        )
 285    }
 286
 287    pub fn go_forward(
 288        workspace: &mut Workspace,
 289        pane: Option<ViewHandle<Pane>>,
 290        cx: &mut ViewContext<Workspace>,
 291    ) -> Task<()> {
 292        Self::navigate_history(
 293            workspace,
 294            pane.unwrap_or_else(|| workspace.active_pane().clone()),
 295            NavigationMode::GoingForward,
 296            cx,
 297        )
 298    }
 299
 300    pub fn reopen_closed_item(
 301        workspace: &mut Workspace,
 302        cx: &mut ViewContext<Workspace>,
 303    ) -> Task<()> {
 304        Self::navigate_history(
 305            workspace,
 306            workspace.active_pane().clone(),
 307            NavigationMode::ReopeningClosedItem,
 308            cx,
 309        )
 310    }
 311
 312    pub fn disable_history(&mut self) {
 313        self.nav_history.borrow_mut().disable();
 314    }
 315
 316    pub fn enable_history(&mut self) {
 317        self.nav_history.borrow_mut().enable();
 318    }
 319
 320    pub fn can_navigate_backward(&self) -> bool {
 321        !self.nav_history.borrow().backward_stack.is_empty()
 322    }
 323
 324    pub fn can_navigate_forward(&self) -> bool {
 325        !self.nav_history.borrow().forward_stack.is_empty()
 326    }
 327
 328    fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
 329        self.toolbar.update(cx, |_, cx| cx.notify());
 330    }
 331
 332    fn navigate_history(
 333        workspace: &mut Workspace,
 334        pane: ViewHandle<Pane>,
 335        mode: NavigationMode,
 336        cx: &mut ViewContext<Workspace>,
 337    ) -> Task<()> {
 338        cx.focus(pane.clone());
 339
 340        let to_load = pane.update(cx, |pane, cx| {
 341            loop {
 342                // Retrieve the weak item handle from the history.
 343                let entry = pane.nav_history.borrow_mut().pop(mode, cx)?;
 344
 345                // If the item is still present in this pane, then activate it.
 346                if let Some(index) = entry
 347                    .item
 348                    .upgrade(cx)
 349                    .and_then(|v| pane.index_for_item(v.as_ref()))
 350                {
 351                    let prev_active_item_index = pane.active_item_index;
 352                    pane.nav_history.borrow_mut().set_mode(mode);
 353                    pane.activate_item(index, true, true, false, cx);
 354                    pane.nav_history
 355                        .borrow_mut()
 356                        .set_mode(NavigationMode::Normal);
 357
 358                    let mut navigated = prev_active_item_index != pane.active_item_index;
 359                    if let Some(data) = entry.data {
 360                        navigated |= pane.active_item()?.navigate(data, cx);
 361                    }
 362
 363                    if navigated {
 364                        break None;
 365                    }
 366                }
 367                // If the item is no longer present in this pane, then retrieve its
 368                // project path in order to reopen it.
 369                else {
 370                    break pane
 371                        .nav_history
 372                        .borrow()
 373                        .paths_by_item
 374                        .get(&entry.item.id())
 375                        .cloned()
 376                        .map(|project_path| (project_path, entry));
 377                }
 378            }
 379        });
 380
 381        if let Some((project_path, entry)) = to_load {
 382            // If the item was no longer present, then load it again from its previous path.
 383            let pane = pane.downgrade();
 384            let task = workspace.load_path(project_path, cx);
 385            cx.spawn(|workspace, mut cx| async move {
 386                let task = task.await;
 387                if let Some(pane) = pane.upgrade(&cx) {
 388                    let mut navigated = false;
 389                    if let Some((project_entry_id, build_item)) = task.log_err() {
 390                        let prev_active_item_id = pane.update(&mut cx, |pane, _| {
 391                            pane.nav_history.borrow_mut().set_mode(mode);
 392                            pane.active_item().map(|p| p.id())
 393                        });
 394
 395                        let item = workspace.update(&mut cx, |workspace, cx| {
 396                            Self::open_item(
 397                                workspace,
 398                                pane.clone(),
 399                                project_entry_id,
 400                                true,
 401                                cx,
 402                                build_item,
 403                            )
 404                        });
 405
 406                        pane.update(&mut cx, |pane, cx| {
 407                            navigated |= Some(item.id()) != prev_active_item_id;
 408                            pane.nav_history
 409                                .borrow_mut()
 410                                .set_mode(NavigationMode::Normal);
 411                            if let Some(data) = entry.data {
 412                                navigated |= item.navigate(data, cx);
 413                            }
 414                        });
 415                    }
 416
 417                    if !navigated {
 418                        workspace
 419                            .update(&mut cx, |workspace, cx| {
 420                                Self::navigate_history(workspace, pane, mode, cx)
 421                            })
 422                            .await;
 423                    }
 424                }
 425            })
 426        } else {
 427            Task::ready(())
 428        }
 429    }
 430
 431    pub(crate) fn open_item(
 432        workspace: &mut Workspace,
 433        pane: ViewHandle<Pane>,
 434        project_entry_id: ProjectEntryId,
 435        focus_item: bool,
 436        cx: &mut ViewContext<Workspace>,
 437        build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 438    ) -> Box<dyn ItemHandle> {
 439        let existing_item = pane.update(cx, |pane, cx| {
 440            for (ix, item) in pane.items.iter().enumerate() {
 441                if item.project_path(cx).is_some()
 442                    && item.project_entry_ids(cx).as_slice() == [project_entry_id]
 443                {
 444                    let item = item.boxed_clone();
 445                    pane.activate_item(ix, true, focus_item, true, cx);
 446                    return Some(item);
 447                }
 448            }
 449            None
 450        });
 451        if let Some(existing_item) = existing_item {
 452            existing_item
 453        } else {
 454            let item = pane.update(cx, |_, cx| build_item(cx));
 455            Self::add_item(workspace, pane, item.boxed_clone(), true, focus_item, cx);
 456            item
 457        }
 458    }
 459
 460    pub fn add_item_at(
 461        workspace: &mut Workspace,
 462        pane: ViewHandle<Pane>,
 463        item: Box<dyn ItemHandle>,
 464        activate_pane: bool,
 465        focus_item: bool,
 466        destination_index: Option<usize>,
 467        cx: &mut ViewContext<Workspace>,
 468    ) {
 469        // Prevent adding the same item to the pane more than once.
 470        // If there is already an active item, reorder the desired item to be after it
 471        // and activate it.
 472        if let Some(item_ix) = pane.read(cx).items.iter().position(|i| i.id() == item.id()) {
 473            pane.update(cx, |pane, cx| {
 474                pane.activate_item(item_ix, activate_pane, focus_item, true, cx)
 475            });
 476            return;
 477        }
 478
 479        item.added_to_pane(workspace, pane.clone(), cx);
 480        pane.update(cx, |pane, cx| {
 481            let item_ix = if let Some(destination_index) = destination_index {
 482                destination_index
 483            } else {
 484                // If there is already an active item, then insert the new item
 485                // right after it. Otherwise, adjust the `active_item_index` field
 486                // before activating the new item, so that in the `activate_item`
 487                // method, we can detect that the active item is changing.
 488                if pane.active_item_index < pane.items.len() {
 489                    pane.active_item_index + 1
 490                } else {
 491                    let ix = pane.items.len();
 492                    pane.active_item_index = usize::MAX;
 493                    ix
 494                }
 495            };
 496
 497            cx.reparent(&item);
 498            pane.items.insert(item_ix, item);
 499            pane.activate_item(item_ix, activate_pane, focus_item, false, cx);
 500            cx.notify();
 501        });
 502    }
 503
 504    pub(crate) fn add_item(
 505        workspace: &mut Workspace,
 506        pane: ViewHandle<Pane>,
 507        item: Box<dyn ItemHandle>,
 508        activate_pane: bool,
 509        focus_item: bool,
 510        cx: &mut ViewContext<Workspace>,
 511    ) {
 512        Self::add_item_at(workspace, pane, item, activate_pane, focus_item, None, cx)
 513    }
 514
 515    pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> {
 516        self.items.iter()
 517    }
 518
 519    pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
 520        self.items
 521            .iter()
 522            .filter_map(|item| item.to_any().downcast())
 523    }
 524
 525    pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
 526        self.items.get(self.active_item_index).cloned()
 527    }
 528
 529    pub fn item_for_entry(
 530        &self,
 531        entry_id: ProjectEntryId,
 532        cx: &AppContext,
 533    ) -> Option<Box<dyn ItemHandle>> {
 534        self.items.iter().find_map(|item| {
 535            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
 536                Some(item.boxed_clone())
 537            } else {
 538                None
 539            }
 540        })
 541    }
 542
 543    pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
 544        self.items.iter().position(|i| i.id() == item.id())
 545    }
 546
 547    pub fn activate_item(
 548        &mut self,
 549        mut index: usize,
 550        activate_pane: bool,
 551        focus_item: bool,
 552        move_after_current_active: bool,
 553        cx: &mut ViewContext<Self>,
 554    ) {
 555        use NavigationMode::{GoingBack, GoingForward};
 556        if index < self.items.len() {
 557            if move_after_current_active {
 558                // If there is already an active item, reorder the desired item to be after it
 559                // and activate it.
 560                if self.active_item_index != index && self.active_item_index < self.items.len() {
 561                    let pane_to_activate = self.items.remove(index);
 562                    if self.active_item_index < index {
 563                        index = self.active_item_index + 1;
 564                    } else if self.active_item_index < self.items.len() + 1 {
 565                        index = self.active_item_index;
 566                        // Index is less than active_item_index. Reordering will decrement the
 567                        // active_item_index, so adjust it accordingly
 568                        self.active_item_index = index - 1;
 569                    }
 570                    self.items.insert(index, pane_to_activate);
 571                }
 572            }
 573
 574            let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
 575            if prev_active_item_ix != self.active_item_index
 576                || matches!(self.nav_history.borrow().mode, GoingBack | GoingForward)
 577            {
 578                if let Some(prev_item) = self.items.get(prev_active_item_ix) {
 579                    prev_item.deactivated(cx);
 580                }
 581                cx.emit(Event::ActivateItem {
 582                    local: activate_pane,
 583                });
 584            }
 585            self.update_toolbar(cx);
 586            if focus_item {
 587                self.focus_active_item(cx);
 588            }
 589            if activate_pane {
 590                cx.emit(Event::Focused);
 591            }
 592            self.autoscroll = true;
 593            cx.notify();
 594        }
 595    }
 596
 597    pub fn activate_prev_item(&mut self, cx: &mut ViewContext<Self>) {
 598        let mut index = self.active_item_index;
 599        if index > 0 {
 600            index -= 1;
 601        } else if !self.items.is_empty() {
 602            index = self.items.len() - 1;
 603        }
 604        self.activate_item(index, true, true, false, cx);
 605    }
 606
 607    pub fn activate_next_item(&mut self, cx: &mut ViewContext<Self>) {
 608        let mut index = self.active_item_index;
 609        if index + 1 < self.items.len() {
 610            index += 1;
 611        } else {
 612            index = 0;
 613        }
 614        self.activate_item(index, true, true, false, cx);
 615    }
 616
 617    pub fn close_active_item(
 618        workspace: &mut Workspace,
 619        _: &CloseActiveItem,
 620        cx: &mut ViewContext<Workspace>,
 621    ) -> Option<Task<Result<()>>> {
 622        let pane_handle = workspace.active_pane().clone();
 623        let pane = pane_handle.read(cx);
 624        if pane.items.is_empty() {
 625            None
 626        } else {
 627            let item_id_to_close = pane.items[pane.active_item_index].id();
 628            let task = Self::close_items(workspace, pane_handle, cx, move |item_id| {
 629                item_id == item_id_to_close
 630            });
 631            Some(cx.foreground().spawn(async move {
 632                task.await?;
 633                Ok(())
 634            }))
 635        }
 636    }
 637
 638    pub fn close_inactive_items(
 639        workspace: &mut Workspace,
 640        _: &CloseInactiveItems,
 641        cx: &mut ViewContext<Workspace>,
 642    ) -> Option<Task<Result<()>>> {
 643        let pane_handle = workspace.active_pane().clone();
 644        let pane = pane_handle.read(cx);
 645        if pane.items.is_empty() {
 646            None
 647        } else {
 648            let active_item_id = pane.items[pane.active_item_index].id();
 649            let task =
 650                Self::close_items(workspace, pane_handle, cx, move |id| id != active_item_id);
 651            Some(cx.foreground().spawn(async move {
 652                task.await?;
 653                Ok(())
 654            }))
 655        }
 656    }
 657
 658    pub fn close_item(
 659        workspace: &mut Workspace,
 660        pane: ViewHandle<Pane>,
 661        item_id_to_close: usize,
 662        cx: &mut ViewContext<Workspace>,
 663    ) -> Task<Result<bool>> {
 664        Self::close_items(workspace, pane, cx, move |view_id| {
 665            view_id == item_id_to_close
 666        })
 667    }
 668
 669    pub fn close_items(
 670        workspace: &mut Workspace,
 671        pane: ViewHandle<Pane>,
 672        cx: &mut ViewContext<Workspace>,
 673        should_close: impl 'static + Fn(usize) -> bool,
 674    ) -> Task<Result<bool>> {
 675        let project = workspace.project().clone();
 676
 677        // Find the items to close.
 678        let mut items_to_close = Vec::new();
 679        for item in &pane.read(cx).items {
 680            if should_close(item.id()) {
 681                items_to_close.push(item.boxed_clone());
 682            }
 683        }
 684
 685        // If a buffer is open both in a singleton editor and in a multibuffer, make sure
 686        // to focus the singleton buffer when prompting to save that buffer, as opposed
 687        // to focusing the multibuffer, because this gives the user a more clear idea
 688        // of what content they would be saving.
 689        items_to_close.sort_by_key(|item| !item.is_singleton(cx));
 690
 691        cx.spawn(|workspace, mut cx| async move {
 692            let mut saved_project_entry_ids = HashSet::default();
 693            for item in items_to_close.clone() {
 694                // Find the item's current index and its set of project entries. Avoid
 695                // storing these in advance, in case they have changed since this task
 696                // was started.
 697                let (item_ix, mut project_entry_ids) = pane.read_with(&cx, |pane, cx| {
 698                    (pane.index_for_item(&*item), item.project_entry_ids(cx))
 699                });
 700                let item_ix = if let Some(ix) = item_ix {
 701                    ix
 702                } else {
 703                    continue;
 704                };
 705
 706                // If an item hasn't yet been associated with a project entry, then always
 707                // prompt to save it before closing it. Otherwise, check if the item has
 708                // any project entries that are not open anywhere else in the workspace,
 709                // AND that the user has not already been prompted to save. If there are
 710                // any such project entries, prompt the user to save this item.
 711                let should_save = if project_entry_ids.is_empty() {
 712                    true
 713                } else {
 714                    workspace.read_with(&cx, |workspace, cx| {
 715                        for item in workspace.items(cx) {
 716                            if !items_to_close
 717                                .iter()
 718                                .any(|item_to_close| item_to_close.id() == item.id())
 719                            {
 720                                let other_project_entry_ids = item.project_entry_ids(cx);
 721                                project_entry_ids
 722                                    .retain(|id| !other_project_entry_ids.contains(id));
 723                            }
 724                        }
 725                    });
 726                    project_entry_ids
 727                        .iter()
 728                        .any(|id| saved_project_entry_ids.insert(*id))
 729                };
 730
 731                if should_save
 732                    && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx)
 733                        .await?
 734                {
 735                    break;
 736                }
 737
 738                // Remove the item from the pane.
 739                pane.update(&mut cx, |pane, cx| {
 740                    if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
 741                        pane.remove_item(item_ix, cx);
 742                    }
 743                });
 744            }
 745
 746            pane.update(&mut cx, |_, cx| cx.notify());
 747            Ok(true)
 748        })
 749    }
 750
 751    fn remove_item(&mut self, item_ix: usize, cx: &mut ViewContext<Self>) {
 752        if item_ix == self.active_item_index {
 753            // Activate the previous item if possible.
 754            // This returns the user to the previously opened tab if they closed
 755            // a new item they just navigated to.
 756            if item_ix > 0 {
 757                self.activate_prev_item(cx);
 758            } else if item_ix + 1 < self.items.len() {
 759                self.activate_next_item(cx);
 760            }
 761        }
 762
 763        let item = self.items.remove(item_ix);
 764        cx.emit(Event::RemoveItem);
 765        if self.items.is_empty() {
 766            item.deactivated(cx);
 767            self.update_toolbar(cx);
 768            cx.emit(Event::Remove);
 769        }
 770
 771        if item_ix < self.active_item_index {
 772            self.active_item_index -= 1;
 773        }
 774
 775        self.nav_history
 776            .borrow_mut()
 777            .set_mode(NavigationMode::ClosingItem);
 778        item.deactivated(cx);
 779        self.nav_history
 780            .borrow_mut()
 781            .set_mode(NavigationMode::Normal);
 782
 783        if let Some(path) = item.project_path(cx) {
 784            self.nav_history
 785                .borrow_mut()
 786                .paths_by_item
 787                .insert(item.id(), path);
 788        } else {
 789            self.nav_history
 790                .borrow_mut()
 791                .paths_by_item
 792                .remove(&item.id());
 793        }
 794
 795        cx.notify();
 796    }
 797
 798    pub async fn save_item(
 799        project: ModelHandle<Project>,
 800        pane: &ViewHandle<Pane>,
 801        item_ix: usize,
 802        item: &dyn ItemHandle,
 803        should_prompt_for_save: bool,
 804        cx: &mut AsyncAppContext,
 805    ) -> Result<bool> {
 806        const CONFLICT_MESSAGE: &str =
 807            "This file has changed on disk since you started editing it. Do you want to overwrite it?";
 808        const DIRTY_MESSAGE: &str = "This file contains unsaved edits. Do you want to save it?";
 809
 810        let (has_conflict, is_dirty, can_save, is_singleton) = cx.read(|cx| {
 811            (
 812                item.has_conflict(cx),
 813                item.is_dirty(cx),
 814                item.can_save(cx),
 815                item.is_singleton(cx),
 816            )
 817        });
 818
 819        if has_conflict && can_save {
 820            let mut answer = pane.update(cx, |pane, cx| {
 821                pane.activate_item(item_ix, true, true, false, cx);
 822                cx.prompt(
 823                    PromptLevel::Warning,
 824                    CONFLICT_MESSAGE,
 825                    &["Overwrite", "Discard", "Cancel"],
 826                )
 827            });
 828            match answer.next().await {
 829                Some(0) => cx.update(|cx| item.save(project, cx)).await?,
 830                Some(1) => cx.update(|cx| item.reload(project, cx)).await?,
 831                _ => return Ok(false),
 832            }
 833        } else if is_dirty && (can_save || is_singleton) {
 834            let will_autosave = cx.read(|cx| {
 835                matches!(
 836                    cx.global::<Settings>().autosave,
 837                    Autosave::OnFocusChange | Autosave::OnWindowChange
 838                ) && Self::can_autosave_item(&*item, cx)
 839            });
 840            let should_save = if should_prompt_for_save && !will_autosave {
 841                let mut answer = pane.update(cx, |pane, cx| {
 842                    pane.activate_item(item_ix, true, true, false, cx);
 843                    cx.prompt(
 844                        PromptLevel::Warning,
 845                        DIRTY_MESSAGE,
 846                        &["Save", "Don't Save", "Cancel"],
 847                    )
 848                });
 849                match answer.next().await {
 850                    Some(0) => true,
 851                    Some(1) => false,
 852                    _ => return Ok(false),
 853                }
 854            } else {
 855                true
 856            };
 857
 858            if should_save {
 859                if can_save {
 860                    cx.update(|cx| item.save(project, cx)).await?;
 861                } else if is_singleton {
 862                    let start_abs_path = project
 863                        .read_with(cx, |project, cx| {
 864                            let worktree = project.visible_worktrees(cx).next()?;
 865                            Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
 866                        })
 867                        .unwrap_or_else(|| Path::new("").into());
 868
 869                    let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
 870                    if let Some(abs_path) = abs_path.next().await.flatten() {
 871                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
 872                    } else {
 873                        return Ok(false);
 874                    }
 875                }
 876            }
 877        }
 878        Ok(true)
 879    }
 880
 881    fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
 882        let is_deleted = item.project_entry_ids(cx).is_empty();
 883        item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
 884    }
 885
 886    pub fn autosave_item(
 887        item: &dyn ItemHandle,
 888        project: ModelHandle<Project>,
 889        cx: &mut MutableAppContext,
 890    ) -> Task<Result<()>> {
 891        if Self::can_autosave_item(item, cx) {
 892            item.save(project, cx)
 893        } else {
 894            Task::ready(Ok(()))
 895        }
 896    }
 897
 898    pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
 899        if let Some(active_item) = self.active_item() {
 900            cx.focus(active_item);
 901        }
 902    }
 903
 904    pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
 905        cx.emit(Event::Split(direction));
 906    }
 907
 908    fn deploy_split_menu(&mut self, action: &DeploySplitMenu, cx: &mut ViewContext<Self>) {
 909        self.context_menu.update(cx, |menu, cx| {
 910            menu.show(
 911                action.position,
 912                vec![
 913                    ContextMenuItem::item("Split Right", SplitRight),
 914                    ContextMenuItem::item("Split Left", SplitLeft),
 915                    ContextMenuItem::item("Split Up", SplitUp),
 916                    ContextMenuItem::item("Split Down", SplitDown),
 917                ],
 918                cx,
 919            );
 920        });
 921    }
 922
 923    fn deploy_new_menu(&mut self, action: &DeployNewMenu, cx: &mut ViewContext<Self>) {
 924        self.context_menu.update(cx, |menu, cx| {
 925            menu.show(
 926                action.position,
 927                vec![
 928                    ContextMenuItem::item("New File", NewFile),
 929                    ContextMenuItem::item("New Terminal", NewTerminal),
 930                    ContextMenuItem::item("New Search", NewSearch),
 931                ],
 932                cx,
 933            );
 934        });
 935    }
 936
 937    pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
 938        &self.toolbar
 939    }
 940
 941    fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
 942        let active_item = self
 943            .items
 944            .get(self.active_item_index)
 945            .map(|item| item.as_ref());
 946        self.toolbar.update(cx, |toolbar, cx| {
 947            toolbar.set_active_pane_item(active_item, cx);
 948        });
 949    }
 950
 951    fn render_tab_bar(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
 952        let theme = cx.global::<Settings>().theme.clone();
 953
 954        struct DraggedItem {
 955            item: Box<dyn ItemHandle>,
 956            pane: WeakViewHandle<Pane>,
 957        }
 958
 959        enum Tabs {}
 960        enum Tab {}
 961        let pane = cx.handle();
 962        MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| {
 963            let autoscroll = if mem::take(&mut self.autoscroll) {
 964                Some(self.active_item_index)
 965            } else {
 966                None
 967            };
 968
 969            let pane_active = self.is_active;
 970
 971            let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
 972            for (ix, (item, detail)) in self
 973                .items
 974                .iter()
 975                .cloned()
 976                .zip(self.tab_details(cx))
 977                .enumerate()
 978            {
 979                let detail = if detail == 0 { None } else { Some(detail) };
 980                let tab_active = ix == self.active_item_index;
 981
 982                row.add_child({
 983                    MouseEventHandler::new::<Tab, _, _>(ix, cx, {
 984                        let item = item.clone();
 985                        let pane = pane.clone();
 986                        let detail = detail.clone();
 987                        let hovered = mouse_state.hovered;
 988
 989                        move |_, cx| {
 990                            Self::render_tab(
 991                                &item,
 992                                pane,
 993                                detail,
 994                                hovered,
 995                                pane_active,
 996                                tab_active,
 997                                cx,
 998                            )
 999                        }
1000                    })
1001                    .with_cursor_style(if pane_active && tab_active {
1002                        CursorStyle::Arrow
1003                    } else {
1004                        CursorStyle::PointingHand
1005                    })
1006                    .on_down(MouseButton::Left, move |_, cx| {
1007                        cx.dispatch_action(ActivateItem(ix));
1008                    })
1009                    .on_click(MouseButton::Middle, {
1010                        let item = item.clone();
1011                        let pane = pane.clone();
1012                        move |_, cx: &mut EventContext| {
1013                            cx.dispatch_action(CloseItem {
1014                                item_id: item.id(),
1015                                pane: pane.clone(),
1016                            })
1017                        }
1018                    })
1019                    .on_up(MouseButton::Left, {
1020                        let pane = pane.clone();
1021                        move |_, cx: &mut EventContext| {
1022                            if let Some((_, dragged_item)) = cx
1023                                .global::<DragAndDrop<Workspace>>()
1024                                .currently_dragged::<DraggedItem>()
1025                            {
1026                                cx.dispatch_action(MoveItem {
1027                                    item_id: dragged_item.item.id(),
1028                                    from: dragged_item.pane.clone(),
1029                                    to: pane.clone(),
1030                                    destination_index: ix,
1031                                })
1032                            }
1033                            cx.propogate_event();
1034                        }
1035                    })
1036                    .as_draggable(
1037                        DraggedItem {
1038                            item,
1039                            pane: pane.clone(),
1040                        },
1041                        {
1042                            let detail = detail.clone();
1043                            move |dragged_item, cx: &mut RenderContext<Workspace>| {
1044                                Pane::render_tab(
1045                                    &dragged_item.item,
1046                                    dragged_item.pane.clone(),
1047                                    detail,
1048                                    false,
1049                                    pane_active,
1050                                    tab_active,
1051                                    cx,
1052                                )
1053                            }
1054                        },
1055                    )
1056                    .boxed()
1057                })
1058            }
1059
1060            let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
1061            row.add_child(
1062                Empty::new()
1063                    .contained()
1064                    .with_style(filler_style.container)
1065                    .with_border(filler_style.container.border)
1066                    .flex(0., true)
1067                    .named("filler"),
1068            );
1069
1070            row.boxed()
1071        })
1072    }
1073
1074    fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
1075        let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
1076
1077        let mut tab_descriptions = HashMap::default();
1078        let mut done = false;
1079        while !done {
1080            done = true;
1081
1082            // Store item indices by their tab description.
1083            for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
1084                if let Some(description) = item.tab_description(*detail, cx) {
1085                    if *detail == 0
1086                        || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
1087                    {
1088                        tab_descriptions
1089                            .entry(description)
1090                            .or_insert(Vec::new())
1091                            .push(ix);
1092                    }
1093                }
1094            }
1095
1096            // If two or more items have the same tab description, increase their level
1097            // of detail and try again.
1098            for (_, item_ixs) in tab_descriptions.drain() {
1099                if item_ixs.len() > 1 {
1100                    done = false;
1101                    for ix in item_ixs {
1102                        tab_details[ix] += 1;
1103                    }
1104                }
1105            }
1106        }
1107
1108        tab_details
1109    }
1110
1111    fn render_tab<V: View>(
1112        item: &Box<dyn ItemHandle>,
1113        pane: WeakViewHandle<Pane>,
1114        detail: Option<usize>,
1115        hovered: bool,
1116        pane_active: bool,
1117        tab_active: bool,
1118        cx: &mut RenderContext<V>,
1119    ) -> ElementBox {
1120        let theme = cx.global::<Settings>().theme.clone();
1121        let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active);
1122        let title = item.tab_content(detail, tab_style, cx);
1123
1124        Flex::row()
1125            .with_child(
1126                Align::new({
1127                    let diameter = 7.0;
1128                    let icon_color = if item.has_conflict(cx) {
1129                        Some(tab_style.icon_conflict)
1130                    } else if item.is_dirty(cx) {
1131                        Some(tab_style.icon_dirty)
1132                    } else {
1133                        None
1134                    };
1135
1136                    ConstrainedBox::new(
1137                        Canvas::new(move |bounds, _, cx| {
1138                            if let Some(color) = icon_color {
1139                                let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
1140                                cx.scene.push_quad(Quad {
1141                                    bounds: square,
1142                                    background: Some(color),
1143                                    border: Default::default(),
1144                                    corner_radius: diameter / 2.,
1145                                });
1146                            }
1147                        })
1148                        .boxed(),
1149                    )
1150                    .with_width(diameter)
1151                    .with_height(diameter)
1152                    .boxed()
1153                })
1154                .boxed(),
1155            )
1156            .with_child(
1157                Container::new(Align::new(title).boxed())
1158                    .with_style(ContainerStyle {
1159                        margin: Margin {
1160                            left: tab_style.spacing,
1161                            right: tab_style.spacing,
1162                            ..Default::default()
1163                        },
1164                        ..Default::default()
1165                    })
1166                    .boxed(),
1167            )
1168            .with_child(
1169                Align::new(
1170                    ConstrainedBox::new(if hovered {
1171                        let item_id = item.id();
1172                        enum TabCloseButton {}
1173                        let icon = Svg::new("icons/x_mark_thin_8.svg");
1174                        MouseEventHandler::new::<TabCloseButton, _, _>(
1175                            item_id,
1176                            cx,
1177                            |mouse_state, _| {
1178                                if mouse_state.hovered {
1179                                    icon.with_color(tab_style.icon_close_active).boxed()
1180                                } else {
1181                                    icon.with_color(tab_style.icon_close).boxed()
1182                                }
1183                            },
1184                        )
1185                        .with_padding(Padding::uniform(4.))
1186                        .with_cursor_style(CursorStyle::PointingHand)
1187                        .on_click(MouseButton::Left, {
1188                            let pane = pane.clone();
1189                            move |_, cx| {
1190                                cx.dispatch_action(CloseItem {
1191                                    item_id,
1192                                    pane: pane.clone(),
1193                                })
1194                            }
1195                        })
1196                        .on_click(MouseButton::Middle, |_, cx| cx.propogate_event())
1197                        .named("close-tab-icon")
1198                    } else {
1199                        Empty::new().boxed()
1200                    })
1201                    .with_width(tab_style.icon_width)
1202                    .boxed(),
1203                )
1204                .boxed(),
1205            )
1206            .contained()
1207            .with_style(tab_style.container)
1208            .constrained()
1209            .with_height(tab_style.height)
1210            .boxed()
1211    }
1212}
1213
1214impl Entity for Pane {
1215    type Event = Event;
1216}
1217
1218impl View for Pane {
1219    fn ui_name() -> &'static str {
1220        "Pane"
1221    }
1222
1223    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
1224        enum SplitIcon {}
1225
1226        let this = cx.handle();
1227
1228        Stack::new()
1229            .with_child(
1230                EventHandler::new(if let Some(active_item) = self.active_item() {
1231                    Flex::column()
1232                        .with_child({
1233                            let mut tab_row = Flex::row()
1234                                .with_child(self.render_tab_bar(cx).flex(1., true).named("tabs"));
1235
1236                            if self.is_active {
1237                                tab_row.add_children([
1238                                    MouseEventHandler::new::<SplitIcon, _, _>(
1239                                        0,
1240                                        cx,
1241                                        |mouse_state, cx| {
1242                                            let theme =
1243                                                &cx.global::<Settings>().theme.workspace.tab_bar;
1244                                            let style =
1245                                                theme.pane_button.style_for(mouse_state, false);
1246                                            Svg::new("icons/plus_12.svg")
1247                                                .with_color(style.color)
1248                                                .constrained()
1249                                                .with_width(style.icon_width)
1250                                                .aligned()
1251                                                .contained()
1252                                                .with_style(style.container)
1253                                                .constrained()
1254                                                .with_width(style.button_width)
1255                                                .with_height(style.button_width)
1256                                                .aligned()
1257                                                .boxed()
1258                                        },
1259                                    )
1260                                    .with_cursor_style(CursorStyle::PointingHand)
1261                                    .on_down(MouseButton::Left, |e, cx| {
1262                                        cx.dispatch_action(DeployNewMenu {
1263                                            position: e.position,
1264                                        });
1265                                    })
1266                                    .boxed(),
1267                                    MouseEventHandler::new::<SplitIcon, _, _>(
1268                                        1,
1269                                        cx,
1270                                        |mouse_state, cx| {
1271                                            let theme =
1272                                                &cx.global::<Settings>().theme.workspace.tab_bar;
1273                                            let style =
1274                                                theme.pane_button.style_for(mouse_state, false);
1275                                            Svg::new("icons/split_12.svg")
1276                                                .with_color(style.color)
1277                                                .constrained()
1278                                                .with_width(style.icon_width)
1279                                                .aligned()
1280                                                .contained()
1281                                                .with_style(style.container)
1282                                                .constrained()
1283                                                .with_width(style.button_width)
1284                                                .with_height(style.button_width)
1285                                                .aligned()
1286                                                .boxed()
1287                                        },
1288                                    )
1289                                    .with_cursor_style(CursorStyle::PointingHand)
1290                                    .on_down(MouseButton::Left, |e, cx| {
1291                                        cx.dispatch_action(DeploySplitMenu {
1292                                            position: e.position,
1293                                        });
1294                                    })
1295                                    .boxed(),
1296                                ])
1297                            }
1298
1299                            tab_row
1300                                .constrained()
1301                                .with_height(cx.global::<Settings>().theme.workspace.tab_bar.height)
1302                                .boxed()
1303                        })
1304                        .with_child(ChildView::new(&self.toolbar).boxed())
1305                        .with_child(ChildView::new(active_item).flex(1., true).boxed())
1306                        .boxed()
1307                } else {
1308                    enum EmptyPane {}
1309                    let theme = cx.global::<Settings>().theme.clone();
1310
1311                    MouseEventHandler::new::<EmptyPane, _, _>(0, cx, |_, _| {
1312                        Empty::new()
1313                            .contained()
1314                            .with_background_color(theme.workspace.background)
1315                            .boxed()
1316                    })
1317                    .on_down(MouseButton::Left, |_, cx| {
1318                        cx.focus_parent_view();
1319                    })
1320                    .boxed()
1321                })
1322                .on_navigate_mouse_down(move |direction, cx| {
1323                    let this = this.clone();
1324                    match direction {
1325                        NavigationDirection::Back => {
1326                            cx.dispatch_action(GoBack { pane: Some(this) })
1327                        }
1328                        NavigationDirection::Forward => {
1329                            cx.dispatch_action(GoForward { pane: Some(this) })
1330                        }
1331                    }
1332
1333                    true
1334                })
1335                .boxed(),
1336            )
1337            .with_child(ChildView::new(&self.context_menu).boxed())
1338            .named("pane")
1339    }
1340
1341    fn on_focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
1342        if cx.is_self_focused() {
1343            if let Some(last_focused_view) = self
1344                .last_focused_view
1345                .as_ref()
1346                .and_then(|handle| handle.upgrade(cx))
1347            {
1348                cx.focus(last_focused_view);
1349            } else {
1350                self.focus_active_item(cx);
1351            }
1352        } else {
1353            self.last_focused_view = Some(focused.downgrade());
1354        }
1355        cx.emit(Event::Focused);
1356    }
1357}
1358
1359impl ItemNavHistory {
1360    pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut MutableAppContext) {
1361        self.history.borrow_mut().push(data, self.item.clone(), cx);
1362    }
1363
1364    pub fn pop_backward(&self, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
1365        self.history.borrow_mut().pop(NavigationMode::GoingBack, cx)
1366    }
1367
1368    pub fn pop_forward(&self, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
1369        self.history
1370            .borrow_mut()
1371            .pop(NavigationMode::GoingForward, cx)
1372    }
1373}
1374
1375impl NavHistory {
1376    fn set_mode(&mut self, mode: NavigationMode) {
1377        self.mode = mode;
1378    }
1379
1380    fn disable(&mut self) {
1381        self.mode = NavigationMode::Disabled;
1382    }
1383
1384    fn enable(&mut self) {
1385        self.mode = NavigationMode::Normal;
1386    }
1387
1388    fn pop(&mut self, mode: NavigationMode, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
1389        let entry = match mode {
1390            NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
1391                return None
1392            }
1393            NavigationMode::GoingBack => &mut self.backward_stack,
1394            NavigationMode::GoingForward => &mut self.forward_stack,
1395            NavigationMode::ReopeningClosedItem => &mut self.closed_stack,
1396        }
1397        .pop_back();
1398        if entry.is_some() {
1399            self.did_update(cx);
1400        }
1401        entry
1402    }
1403
1404    fn push<D: 'static + Any>(
1405        &mut self,
1406        data: Option<D>,
1407        item: Rc<dyn WeakItemHandle>,
1408        cx: &mut MutableAppContext,
1409    ) {
1410        match self.mode {
1411            NavigationMode::Disabled => {}
1412            NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
1413                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1414                    self.backward_stack.pop_front();
1415                }
1416                self.backward_stack.push_back(NavigationEntry {
1417                    item,
1418                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1419                });
1420                self.forward_stack.clear();
1421            }
1422            NavigationMode::GoingBack => {
1423                if self.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1424                    self.forward_stack.pop_front();
1425                }
1426                self.forward_stack.push_back(NavigationEntry {
1427                    item,
1428                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1429                });
1430            }
1431            NavigationMode::GoingForward => {
1432                if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1433                    self.backward_stack.pop_front();
1434                }
1435                self.backward_stack.push_back(NavigationEntry {
1436                    item,
1437                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1438                });
1439            }
1440            NavigationMode::ClosingItem => {
1441                if self.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
1442                    self.closed_stack.pop_front();
1443                }
1444                self.closed_stack.push_back(NavigationEntry {
1445                    item,
1446                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
1447                });
1448            }
1449        }
1450        self.did_update(cx);
1451    }
1452
1453    fn did_update(&self, cx: &mut MutableAppContext) {
1454        if let Some(pane) = self.pane.upgrade(cx) {
1455            cx.defer(move |cx| pane.update(cx, |pane, cx| pane.history_updated(cx)));
1456        }
1457    }
1458}