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