pane.rs

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