pane.rs

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