workspace.rs

   1/// NOTE: Focus only 'takes' after an update has flushed_effects. Pane sends an event in on_focus_in
   2/// which the workspace uses to change the activated pane.
   3///
   4/// This may cause issues when you're trying to write tests that use workspace focus to add items at
   5/// specific locations.
   6pub mod dock;
   7pub mod pane;
   8pub mod pane_group;
   9pub mod searchable;
  10pub mod sidebar;
  11mod status_bar;
  12mod toolbar;
  13
  14use anyhow::{anyhow, Context, Result};
  15use call::ActiveCall;
  16use client::{proto, Client, PeerId, TypedEnvelope, UserStore};
  17use collections::{hash_map, HashMap, HashSet};
  18use dock::{DefaultItemFactory, Dock, ToggleDockButton};
  19use drag_and_drop::DragAndDrop;
  20use futures::{channel::oneshot, FutureExt};
  21use gpui::{
  22    actions,
  23    elements::*,
  24    impl_actions, impl_internal_actions,
  25    platform::{CursorStyle, WindowOptions},
  26    AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
  27    MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View,
  28    ViewContext, ViewHandle, WeakViewHandle,
  29};
  30use language::LanguageRegistry;
  31use log::{error, warn};
  32pub use pane::*;
  33pub use pane_group::*;
  34use postage::prelude::Stream;
  35use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
  36use searchable::SearchableItemHandle;
  37use serde::Deserialize;
  38use settings::{Autosave, DockAnchor, Settings};
  39use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem};
  40use smallvec::SmallVec;
  41use status_bar::StatusBar;
  42pub use status_bar::StatusItemView;
  43use std::{
  44    any::{Any, TypeId},
  45    borrow::Cow,
  46    cell::RefCell,
  47    fmt,
  48    future::Future,
  49    mem,
  50    path::{Path, PathBuf},
  51    rc::Rc,
  52    sync::{
  53        atomic::{AtomicBool, Ordering::SeqCst},
  54        Arc,
  55    },
  56    time::Duration,
  57};
  58use theme::{Theme, ThemeRegistry};
  59pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  60use util::ResultExt;
  61
  62type ProjectItemBuilders = HashMap<
  63    TypeId,
  64    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
  65>;
  66
  67type FollowableItemBuilder = fn(
  68    ViewHandle<Pane>,
  69    ModelHandle<Project>,
  70    &mut Option<proto::view::Variant>,
  71    &mut MutableAppContext,
  72) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
  73type FollowableItemBuilders = HashMap<
  74    TypeId,
  75    (
  76        FollowableItemBuilder,
  77        fn(AnyViewHandle) -> Box<dyn FollowableItemHandle>,
  78    ),
  79>;
  80
  81#[derive(Clone, PartialEq)]
  82pub struct RemoveWorktreeFromProject(pub WorktreeId);
  83
  84actions!(
  85    workspace,
  86    [
  87        Open,
  88        NewFile,
  89        NewWindow,
  90        CloseWindow,
  91        AddFolderToProject,
  92        Unfollow,
  93        Save,
  94        SaveAs,
  95        SaveAll,
  96        ActivatePreviousPane,
  97        ActivateNextPane,
  98        FollowNextCollaborator,
  99        ToggleLeftSidebar,
 100        ToggleRightSidebar,
 101        NewTerminal,
 102        NewSearch
 103    ]
 104);
 105
 106#[derive(Clone, PartialEq)]
 107pub struct OpenPaths {
 108    pub paths: Vec<PathBuf>,
 109}
 110
 111#[derive(Clone, Deserialize, PartialEq)]
 112pub struct ActivatePane(pub usize);
 113
 114#[derive(Clone, PartialEq)]
 115pub struct ToggleFollow(pub PeerId);
 116
 117#[derive(Clone, PartialEq)]
 118pub struct JoinProject {
 119    pub project_id: u64,
 120    pub follow_user_id: u64,
 121}
 122
 123impl_internal_actions!(
 124    workspace,
 125    [
 126        OpenPaths,
 127        ToggleFollow,
 128        JoinProject,
 129        RemoveWorktreeFromProject
 130    ]
 131);
 132impl_actions!(workspace, [ActivatePane]);
 133
 134pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
 135    pane::init(cx);
 136    dock::init(cx);
 137
 138    cx.add_global_action(open);
 139    cx.add_global_action({
 140        let app_state = Arc::downgrade(&app_state);
 141        move |action: &OpenPaths, cx: &mut MutableAppContext| {
 142            if let Some(app_state) = app_state.upgrade() {
 143                open_paths(&action.paths, &app_state, cx).detach();
 144            }
 145        }
 146    });
 147    cx.add_global_action({
 148        let app_state = Arc::downgrade(&app_state);
 149        move |_: &NewFile, cx: &mut MutableAppContext| {
 150            if let Some(app_state) = app_state.upgrade() {
 151                open_new(&app_state, cx)
 152            }
 153        }
 154    });
 155    cx.add_global_action({
 156        let app_state = Arc::downgrade(&app_state);
 157        move |_: &NewWindow, cx: &mut MutableAppContext| {
 158            if let Some(app_state) = app_state.upgrade() {
 159                open_new(&app_state, cx)
 160            }
 161        }
 162    });
 163
 164    cx.add_async_action(Workspace::toggle_follow);
 165    cx.add_async_action(Workspace::follow_next_collaborator);
 166    cx.add_async_action(Workspace::close);
 167    cx.add_async_action(Workspace::save_all);
 168    cx.add_action(Workspace::add_folder_to_project);
 169    cx.add_action(Workspace::remove_folder_from_project);
 170    cx.add_action(
 171        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 172            let pane = workspace.active_pane().clone();
 173            workspace.unfollow(&pane, cx);
 174        },
 175    );
 176    cx.add_action(
 177        |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
 178            workspace.save_active_item(false, cx).detach_and_log_err(cx);
 179        },
 180    );
 181    cx.add_action(
 182        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 183            workspace.save_active_item(true, cx).detach_and_log_err(cx);
 184        },
 185    );
 186    cx.add_action(Workspace::toggle_sidebar_item);
 187    cx.add_action(Workspace::focus_center);
 188    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 189        workspace.activate_previous_pane(cx)
 190    });
 191    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 192        workspace.activate_next_pane(cx)
 193    });
 194    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftSidebar, cx| {
 195        workspace.toggle_sidebar(SidebarSide::Left, cx);
 196    });
 197    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightSidebar, cx| {
 198        workspace.toggle_sidebar(SidebarSide::Right, cx);
 199    });
 200    cx.add_action(Workspace::activate_pane_at_index);
 201
 202    let client = &app_state.client;
 203    client.add_view_request_handler(Workspace::handle_follow);
 204    client.add_view_message_handler(Workspace::handle_unfollow);
 205    client.add_view_message_handler(Workspace::handle_update_followers);
 206}
 207
 208pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
 209    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 210        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 211            let item = model.downcast::<I::Item>().unwrap();
 212            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 213        });
 214    });
 215}
 216
 217pub fn register_followable_item<I: FollowableItem>(cx: &mut MutableAppContext) {
 218    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 219        builders.insert(
 220            TypeId::of::<I>(),
 221            (
 222                |pane, project, state, cx| {
 223                    I::from_state_proto(pane, project, state, cx).map(|task| {
 224                        cx.foreground()
 225                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 226                    })
 227                },
 228                |this| Box::new(this.downcast::<I>().unwrap()),
 229            ),
 230        );
 231    });
 232}
 233
 234pub struct AppState {
 235    pub languages: Arc<LanguageRegistry>,
 236    pub themes: Arc<ThemeRegistry>,
 237    pub client: Arc<client::Client>,
 238    pub user_store: ModelHandle<client::UserStore>,
 239    pub project_store: ModelHandle<ProjectStore>,
 240    pub fs: Arc<dyn fs::Fs>,
 241    pub build_window_options: fn() -> WindowOptions<'static>,
 242    pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
 243    pub default_item_factory: DefaultItemFactory,
 244}
 245
 246#[derive(Eq, PartialEq, Hash)]
 247pub enum ItemEvent {
 248    CloseItem,
 249    UpdateTab,
 250    UpdateBreadcrumbs,
 251    Edit,
 252}
 253
 254pub trait Item: View {
 255    fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
 256    fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
 257    fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
 258        false
 259    }
 260    fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
 261        None
 262    }
 263    fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
 264        -> ElementBox;
 265    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 266    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
 267    fn is_singleton(&self, cx: &AppContext) -> bool;
 268    fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
 269    fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
 270    where
 271        Self: Sized,
 272    {
 273        None
 274    }
 275    fn is_dirty(&self, _: &AppContext) -> bool {
 276        false
 277    }
 278    fn has_conflict(&self, _: &AppContext) -> bool {
 279        false
 280    }
 281    fn can_save(&self, cx: &AppContext) -> bool;
 282    fn save(
 283        &mut self,
 284        project: ModelHandle<Project>,
 285        cx: &mut ViewContext<Self>,
 286    ) -> Task<Result<()>>;
 287    fn save_as(
 288        &mut self,
 289        project: ModelHandle<Project>,
 290        abs_path: PathBuf,
 291        cx: &mut ViewContext<Self>,
 292    ) -> Task<Result<()>>;
 293    fn reload(
 294        &mut self,
 295        project: ModelHandle<Project>,
 296        cx: &mut ViewContext<Self>,
 297    ) -> Task<Result<()>>;
 298    fn to_item_events(event: &Self::Event) -> Vec<ItemEvent>;
 299    fn act_as_type(
 300        &self,
 301        type_id: TypeId,
 302        self_handle: &ViewHandle<Self>,
 303        _: &AppContext,
 304    ) -> Option<AnyViewHandle> {
 305        if TypeId::of::<Self>() == type_id {
 306            Some(self_handle.into())
 307        } else {
 308            None
 309        }
 310    }
 311    fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
 312        None
 313    }
 314
 315    fn breadcrumb_location(&self) -> ToolbarItemLocation {
 316        ToolbarItemLocation::Hidden
 317    }
 318    fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
 319        None
 320    }
 321}
 322
 323pub trait ProjectItem: Item {
 324    type Item: project::Item;
 325
 326    fn for_project_item(
 327        project: ModelHandle<Project>,
 328        item: ModelHandle<Self::Item>,
 329        cx: &mut ViewContext<Self>,
 330    ) -> Self;
 331}
 332
 333pub trait FollowableItem: Item {
 334    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 335    fn from_state_proto(
 336        pane: ViewHandle<Pane>,
 337        project: ModelHandle<Project>,
 338        state: &mut Option<proto::view::Variant>,
 339        cx: &mut MutableAppContext,
 340    ) -> Option<Task<Result<ViewHandle<Self>>>>;
 341    fn add_event_to_update_proto(
 342        &self,
 343        event: &Self::Event,
 344        update: &mut Option<proto::update_view::Variant>,
 345        cx: &AppContext,
 346    ) -> bool;
 347    fn apply_update_proto(
 348        &mut self,
 349        message: proto::update_view::Variant,
 350        cx: &mut ViewContext<Self>,
 351    ) -> Result<()>;
 352
 353    fn set_leader_replica_id(&mut self, leader_replica_id: Option<u16>, cx: &mut ViewContext<Self>);
 354    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
 355}
 356
 357pub trait FollowableItemHandle: ItemHandle {
 358    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext);
 359    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
 360    fn add_event_to_update_proto(
 361        &self,
 362        event: &dyn Any,
 363        update: &mut Option<proto::update_view::Variant>,
 364        cx: &AppContext,
 365    ) -> bool;
 366    fn apply_update_proto(
 367        &self,
 368        message: proto::update_view::Variant,
 369        cx: &mut MutableAppContext,
 370    ) -> Result<()>;
 371    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
 372}
 373
 374impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
 375    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext) {
 376        self.update(cx, |this, cx| {
 377            this.set_leader_replica_id(leader_replica_id, cx)
 378        })
 379    }
 380
 381    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
 382        self.read(cx).to_state_proto(cx)
 383    }
 384
 385    fn add_event_to_update_proto(
 386        &self,
 387        event: &dyn Any,
 388        update: &mut Option<proto::update_view::Variant>,
 389        cx: &AppContext,
 390    ) -> bool {
 391        if let Some(event) = event.downcast_ref() {
 392            self.read(cx).add_event_to_update_proto(event, update, cx)
 393        } else {
 394            false
 395        }
 396    }
 397
 398    fn apply_update_proto(
 399        &self,
 400        message: proto::update_view::Variant,
 401        cx: &mut MutableAppContext,
 402    ) -> Result<()> {
 403        self.update(cx, |this, cx| this.apply_update_proto(message, cx))
 404    }
 405
 406    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
 407        if let Some(event) = event.downcast_ref() {
 408            T::should_unfollow_on_event(event, cx)
 409        } else {
 410            false
 411        }
 412    }
 413}
 414
 415pub trait ItemHandle: 'static + fmt::Debug {
 416    fn subscribe_to_item_events(
 417        &self,
 418        cx: &mut MutableAppContext,
 419        handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
 420    ) -> gpui::Subscription;
 421    fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
 422    fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
 423        -> ElementBox;
 424    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 425    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
 426    fn is_singleton(&self, cx: &AppContext) -> bool;
 427    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
 428    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
 429    fn added_to_pane(
 430        &self,
 431        workspace: &mut Workspace,
 432        pane: ViewHandle<Pane>,
 433        cx: &mut ViewContext<Workspace>,
 434    );
 435    fn deactivated(&self, cx: &mut MutableAppContext);
 436    fn workspace_deactivated(&self, cx: &mut MutableAppContext);
 437    fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool;
 438    fn id(&self) -> usize;
 439    fn window_id(&self) -> usize;
 440    fn to_any(&self) -> AnyViewHandle;
 441    fn is_dirty(&self, cx: &AppContext) -> bool;
 442    fn has_conflict(&self, cx: &AppContext) -> bool;
 443    fn can_save(&self, cx: &AppContext) -> bool;
 444    fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
 445    fn save_as(
 446        &self,
 447        project: ModelHandle<Project>,
 448        abs_path: PathBuf,
 449        cx: &mut MutableAppContext,
 450    ) -> Task<Result<()>>;
 451    fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
 452        -> Task<Result<()>>;
 453    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
 454    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
 455    fn on_release(
 456        &self,
 457        cx: &mut MutableAppContext,
 458        callback: Box<dyn FnOnce(&mut MutableAppContext)>,
 459    ) -> gpui::Subscription;
 460    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
 461    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
 462    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
 463}
 464
 465pub trait WeakItemHandle {
 466    fn id(&self) -> usize;
 467    fn window_id(&self) -> usize;
 468    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
 469}
 470
 471impl dyn ItemHandle {
 472    pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
 473        self.to_any().downcast()
 474    }
 475
 476    pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
 477        self.act_as_type(TypeId::of::<T>(), cx)
 478            .and_then(|t| t.downcast())
 479    }
 480}
 481
 482impl<T: Item> ItemHandle for ViewHandle<T> {
 483    fn subscribe_to_item_events(
 484        &self,
 485        cx: &mut MutableAppContext,
 486        handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
 487    ) -> gpui::Subscription {
 488        cx.subscribe(self, move |_, event, cx| {
 489            for item_event in T::to_item_events(event) {
 490                handler(item_event, cx)
 491            }
 492        })
 493    }
 494
 495    fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
 496        self.read(cx).tab_description(detail, cx)
 497    }
 498
 499    fn tab_content(
 500        &self,
 501        detail: Option<usize>,
 502        style: &theme::Tab,
 503        cx: &AppContext,
 504    ) -> ElementBox {
 505        self.read(cx).tab_content(detail, style, cx)
 506    }
 507
 508    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
 509        self.read(cx).project_path(cx)
 510    }
 511
 512    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
 513        self.read(cx).project_entry_ids(cx)
 514    }
 515
 516    fn is_singleton(&self, cx: &AppContext) -> bool {
 517        self.read(cx).is_singleton(cx)
 518    }
 519
 520    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
 521        Box::new(self.clone())
 522    }
 523
 524    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
 525        self.update(cx, |item, cx| {
 526            cx.add_option_view(|cx| item.clone_on_split(cx))
 527        })
 528        .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
 529    }
 530
 531    fn added_to_pane(
 532        &self,
 533        workspace: &mut Workspace,
 534        pane: ViewHandle<Pane>,
 535        cx: &mut ViewContext<Workspace>,
 536    ) {
 537        let history = pane.read(cx).nav_history_for_item(self);
 538        self.update(cx, |this, cx| this.set_nav_history(history, cx));
 539
 540        if let Some(followed_item) = self.to_followable_item_handle(cx) {
 541            if let Some(message) = followed_item.to_state_proto(cx) {
 542                workspace.update_followers(
 543                    proto::update_followers::Variant::CreateView(proto::View {
 544                        id: followed_item.id() as u64,
 545                        variant: Some(message),
 546                        leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
 547                    }),
 548                    cx,
 549                );
 550            }
 551        }
 552
 553        if workspace
 554            .panes_by_item
 555            .insert(self.id(), pane.downgrade())
 556            .is_none()
 557        {
 558            let mut pending_autosave = None;
 559            let mut cancel_pending_autosave = oneshot::channel::<()>().0;
 560            let pending_update = Rc::new(RefCell::new(None));
 561            let pending_update_scheduled = Rc::new(AtomicBool::new(false));
 562
 563            let mut event_subscription =
 564                Some(cx.subscribe(self, move |workspace, item, event, cx| {
 565                    let pane = if let Some(pane) = workspace
 566                        .panes_by_item
 567                        .get(&item.id())
 568                        .and_then(|pane| pane.upgrade(cx))
 569                    {
 570                        pane
 571                    } else {
 572                        log::error!("unexpected item event after pane was dropped");
 573                        return;
 574                    };
 575
 576                    if let Some(item) = item.to_followable_item_handle(cx) {
 577                        let leader_id = workspace.leader_for_pane(&pane);
 578
 579                        if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
 580                            workspace.unfollow(&pane, cx);
 581                        }
 582
 583                        if item.add_event_to_update_proto(
 584                            event,
 585                            &mut *pending_update.borrow_mut(),
 586                            cx,
 587                        ) && !pending_update_scheduled.load(SeqCst)
 588                        {
 589                            pending_update_scheduled.store(true, SeqCst);
 590                            cx.after_window_update({
 591                                let pending_update = pending_update.clone();
 592                                let pending_update_scheduled = pending_update_scheduled.clone();
 593                                move |this, cx| {
 594                                    pending_update_scheduled.store(false, SeqCst);
 595                                    this.update_followers(
 596                                        proto::update_followers::Variant::UpdateView(
 597                                            proto::UpdateView {
 598                                                id: item.id() as u64,
 599                                                variant: pending_update.borrow_mut().take(),
 600                                                leader_id: leader_id.map(|id| id.0),
 601                                            },
 602                                        ),
 603                                        cx,
 604                                    );
 605                                }
 606                            });
 607                        }
 608                    }
 609
 610                    for item_event in T::to_item_events(event).into_iter() {
 611                        match item_event {
 612                            ItemEvent::CloseItem => {
 613                                Pane::close_item(workspace, pane, item.id(), cx)
 614                                    .detach_and_log_err(cx);
 615                                return;
 616                            }
 617                            ItemEvent::UpdateTab => {
 618                                pane.update(cx, |_, cx| {
 619                                    cx.emit(pane::Event::ChangeItemTitle);
 620                                    cx.notify();
 621                                });
 622                            }
 623                            ItemEvent::Edit => {
 624                                if let Autosave::AfterDelay { milliseconds } =
 625                                    cx.global::<Settings>().autosave
 626                                {
 627                                    let prev_autosave = pending_autosave
 628                                        .take()
 629                                        .unwrap_or_else(|| Task::ready(Some(())));
 630                                    let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>();
 631                                    let prev_cancel_tx =
 632                                        mem::replace(&mut cancel_pending_autosave, cancel_tx);
 633                                    let project = workspace.project.downgrade();
 634                                    let _ = prev_cancel_tx.send(());
 635                                    let item = item.clone();
 636                                    pending_autosave =
 637                                        Some(cx.spawn_weak(|_, mut cx| async move {
 638                                            let mut timer = cx
 639                                                .background()
 640                                                .timer(Duration::from_millis(milliseconds))
 641                                                .fuse();
 642                                            prev_autosave.await;
 643                                            futures::select_biased! {
 644                                                _ = cancel_rx => return None,
 645                                                    _ = timer => {}
 646                                            }
 647
 648                                            let project = project.upgrade(&cx)?;
 649                                            cx.update(|cx| Pane::autosave_item(&item, project, cx))
 650                                                .await
 651                                                .log_err();
 652                                            None
 653                                        }));
 654                                }
 655                            }
 656                            _ => {}
 657                        }
 658                    }
 659                }));
 660
 661            cx.observe_focus(self, move |workspace, item, focused, cx| {
 662                if !focused && cx.global::<Settings>().autosave == Autosave::OnFocusChange {
 663                    Pane::autosave_item(&item, workspace.project.clone(), cx)
 664                        .detach_and_log_err(cx);
 665                }
 666            })
 667            .detach();
 668
 669            let item_id = self.id();
 670            cx.observe_release(self, move |workspace, _, _| {
 671                workspace.panes_by_item.remove(&item_id);
 672                event_subscription.take();
 673            })
 674            .detach();
 675        }
 676    }
 677
 678    fn deactivated(&self, cx: &mut MutableAppContext) {
 679        self.update(cx, |this, cx| this.deactivated(cx));
 680    }
 681
 682    fn workspace_deactivated(&self, cx: &mut MutableAppContext) {
 683        self.update(cx, |this, cx| this.workspace_deactivated(cx));
 684    }
 685
 686    fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool {
 687        self.update(cx, |this, cx| this.navigate(data, cx))
 688    }
 689
 690    fn id(&self) -> usize {
 691        self.id()
 692    }
 693
 694    fn window_id(&self) -> usize {
 695        self.window_id()
 696    }
 697
 698    fn to_any(&self) -> AnyViewHandle {
 699        self.into()
 700    }
 701
 702    fn is_dirty(&self, cx: &AppContext) -> bool {
 703        self.read(cx).is_dirty(cx)
 704    }
 705
 706    fn has_conflict(&self, cx: &AppContext) -> bool {
 707        self.read(cx).has_conflict(cx)
 708    }
 709
 710    fn can_save(&self, cx: &AppContext) -> bool {
 711        self.read(cx).can_save(cx)
 712    }
 713
 714    fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
 715        self.update(cx, |item, cx| item.save(project, cx))
 716    }
 717
 718    fn save_as(
 719        &self,
 720        project: ModelHandle<Project>,
 721        abs_path: PathBuf,
 722        cx: &mut MutableAppContext,
 723    ) -> Task<anyhow::Result<()>> {
 724        self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
 725    }
 726
 727    fn reload(
 728        &self,
 729        project: ModelHandle<Project>,
 730        cx: &mut MutableAppContext,
 731    ) -> Task<Result<()>> {
 732        self.update(cx, |item, cx| item.reload(project, cx))
 733    }
 734
 735    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
 736        self.read(cx).act_as_type(type_id, self, cx)
 737    }
 738
 739    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
 740        if cx.has_global::<FollowableItemBuilders>() {
 741            let builders = cx.global::<FollowableItemBuilders>();
 742            let item = self.to_any();
 743            Some(builders.get(&item.view_type())?.1(item))
 744        } else {
 745            None
 746        }
 747    }
 748
 749    fn on_release(
 750        &self,
 751        cx: &mut MutableAppContext,
 752        callback: Box<dyn FnOnce(&mut MutableAppContext)>,
 753    ) -> gpui::Subscription {
 754        cx.observe_release(self, move |_, cx| callback(cx))
 755    }
 756
 757    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
 758        self.read(cx).as_searchable(self)
 759    }
 760
 761    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
 762        self.read(cx).breadcrumb_location()
 763    }
 764
 765    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
 766        self.read(cx).breadcrumbs(theme, cx)
 767    }
 768}
 769
 770impl From<Box<dyn ItemHandle>> for AnyViewHandle {
 771    fn from(val: Box<dyn ItemHandle>) -> Self {
 772        val.to_any()
 773    }
 774}
 775
 776impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
 777    fn from(val: &Box<dyn ItemHandle>) -> Self {
 778        val.to_any()
 779    }
 780}
 781
 782impl Clone for Box<dyn ItemHandle> {
 783    fn clone(&self) -> Box<dyn ItemHandle> {
 784        self.boxed_clone()
 785    }
 786}
 787
 788impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
 789    fn id(&self) -> usize {
 790        self.id()
 791    }
 792
 793    fn window_id(&self) -> usize {
 794        self.window_id()
 795    }
 796
 797    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
 798        self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
 799    }
 800}
 801
 802pub trait Notification: View {
 803    fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool;
 804}
 805
 806pub trait NotificationHandle {
 807    fn id(&self) -> usize;
 808    fn to_any(&self) -> AnyViewHandle;
 809}
 810
 811impl<T: Notification> NotificationHandle for ViewHandle<T> {
 812    fn id(&self) -> usize {
 813        self.id()
 814    }
 815
 816    fn to_any(&self) -> AnyViewHandle {
 817        self.into()
 818    }
 819}
 820
 821impl From<&dyn NotificationHandle> for AnyViewHandle {
 822    fn from(val: &dyn NotificationHandle) -> Self {
 823        val.to_any()
 824    }
 825}
 826
 827impl AppState {
 828    #[cfg(any(test, feature = "test-support"))]
 829    pub fn test(cx: &mut MutableAppContext) -> Arc<Self> {
 830        let settings = Settings::test(cx);
 831        cx.set_global(settings);
 832
 833        let fs = project::FakeFs::new(cx.background().clone());
 834        let languages = Arc::new(LanguageRegistry::test());
 835        let http_client = client::test::FakeHttpClient::with_404_response();
 836        let client = Client::new(http_client.clone());
 837        let project_store = cx.add_model(|_| ProjectStore::new());
 838        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 839        let themes = ThemeRegistry::new((), cx.font_cache().clone());
 840        Arc::new(Self {
 841            client,
 842            themes,
 843            fs,
 844            languages,
 845            user_store,
 846            project_store,
 847            initialize_workspace: |_, _, _| {},
 848            build_window_options: Default::default,
 849            default_item_factory: |_, _| unimplemented!(),
 850        })
 851    }
 852}
 853
 854pub enum Event {
 855    DockAnchorChanged,
 856    PaneAdded(ViewHandle<Pane>),
 857    ContactRequestedJoin(u64),
 858}
 859
 860pub struct Workspace {
 861    weak_self: WeakViewHandle<Self>,
 862    client: Arc<Client>,
 863    user_store: ModelHandle<client::UserStore>,
 864    remote_entity_subscription: Option<client::Subscription>,
 865    fs: Arc<dyn Fs>,
 866    modal: Option<AnyViewHandle>,
 867    center: PaneGroup,
 868    left_sidebar: ViewHandle<Sidebar>,
 869    right_sidebar: ViewHandle<Sidebar>,
 870    panes: Vec<ViewHandle<Pane>>,
 871    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 872    active_pane: ViewHandle<Pane>,
 873    last_active_center_pane: Option<ViewHandle<Pane>>,
 874    status_bar: ViewHandle<StatusBar>,
 875    titlebar_item: Option<AnyViewHandle>,
 876    dock: Dock,
 877    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 878    project: ModelHandle<Project>,
 879    leader_state: LeaderState,
 880    follower_states_by_leader: FollowerStatesByLeader,
 881    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 882    window_edited: bool,
 883    _observe_current_user: Task<()>,
 884    _active_call_observation: gpui::Subscription,
 885}
 886
 887#[derive(Default)]
 888struct LeaderState {
 889    followers: HashSet<PeerId>,
 890}
 891
 892type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 893
 894#[derive(Default)]
 895struct FollowerState {
 896    active_view_id: Option<u64>,
 897    items_by_leader_view_id: HashMap<u64, FollowerItem>,
 898}
 899
 900#[derive(Debug)]
 901enum FollowerItem {
 902    Loading(Vec<proto::update_view::Variant>),
 903    Loaded(Box<dyn FollowableItemHandle>),
 904}
 905
 906impl Workspace {
 907    pub fn new(
 908        project: ModelHandle<Project>,
 909        dock_default_factory: DefaultItemFactory,
 910        cx: &mut ViewContext<Self>,
 911    ) -> Self {
 912        cx.observe_fullscreen(|_, _, cx| cx.notify()).detach();
 913
 914        cx.observe_window_activation(Self::on_window_activation_changed)
 915            .detach();
 916        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 917        cx.subscribe(&project, move |this, _, event, cx| {
 918            match event {
 919                project::Event::RemoteIdChanged(remote_id) => {
 920                    this.project_remote_id_changed(*remote_id, cx);
 921                }
 922                project::Event::CollaboratorLeft(peer_id) => {
 923                    this.collaborator_left(*peer_id, cx);
 924                }
 925                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 926                    this.update_window_title(cx);
 927                }
 928                project::Event::DisconnectedFromHost => {
 929                    this.update_window_edited(cx);
 930                    cx.blur();
 931                }
 932                _ => {}
 933            }
 934            cx.notify()
 935        })
 936        .detach();
 937
 938        let center_pane = cx.add_view(|cx| Pane::new(None, cx));
 939        let pane_id = center_pane.id();
 940        cx.subscribe(&center_pane, move |this, _, event, cx| {
 941            this.handle_pane_event(pane_id, event, cx)
 942        })
 943        .detach();
 944        cx.focus(&center_pane);
 945        cx.emit(Event::PaneAdded(center_pane.clone()));
 946
 947        let fs = project.read(cx).fs().clone();
 948        let user_store = project.read(cx).user_store();
 949        let client = project.read(cx).client();
 950        let mut current_user = user_store.read(cx).watch_current_user();
 951        let mut connection_status = client.status();
 952        let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
 953            current_user.recv().await;
 954            connection_status.recv().await;
 955            let mut stream =
 956                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 957
 958            while stream.recv().await.is_some() {
 959                cx.update(|cx| {
 960                    if let Some(this) = this.upgrade(cx) {
 961                        this.update(cx, |_, cx| cx.notify());
 962                    }
 963                })
 964            }
 965        });
 966
 967        let handle = cx.handle();
 968        let weak_handle = cx.weak_handle();
 969
 970        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 971
 972        let dock = Dock::new(cx, dock_default_factory);
 973        let dock_pane = dock.pane().clone();
 974
 975        let left_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Left));
 976        let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right));
 977        let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx));
 978        let toggle_dock = cx.add_view(|cx| ToggleDockButton::new(handle, cx));
 979        let right_sidebar_buttons =
 980            cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx));
 981        let status_bar = cx.add_view(|cx| {
 982            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 983            status_bar.add_left_item(left_sidebar_buttons, cx);
 984            status_bar.add_right_item(right_sidebar_buttons, cx);
 985            status_bar.add_right_item(toggle_dock, cx);
 986            status_bar
 987        });
 988
 989        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 990            drag_and_drop.register_container(weak_handle.clone());
 991        });
 992
 993        let mut this = Workspace {
 994            modal: None,
 995            weak_self: weak_handle,
 996            center: PaneGroup::new(center_pane.clone()),
 997            dock,
 998            // When removing an item, the last element remaining in this array
 999            // is used to find where focus should fallback to. As such, the order
1000            // of these two variables is important.
1001            panes: vec![dock_pane, center_pane.clone()],
1002            panes_by_item: Default::default(),
1003            active_pane: center_pane.clone(),
1004            last_active_center_pane: Some(center_pane.clone()),
1005            status_bar,
1006            titlebar_item: None,
1007            notifications: Default::default(),
1008            client,
1009            remote_entity_subscription: None,
1010            user_store,
1011            fs,
1012            left_sidebar,
1013            right_sidebar,
1014            project,
1015            leader_state: Default::default(),
1016            follower_states_by_leader: Default::default(),
1017            last_leaders_by_pane: Default::default(),
1018            window_edited: false,
1019            _observe_current_user,
1020            _active_call_observation: cx.observe(&ActiveCall::global(cx), |_, _, cx| cx.notify()),
1021        };
1022        this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
1023        cx.defer(|this, cx| this.update_window_title(cx));
1024
1025        this
1026    }
1027
1028    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
1029        self.weak_self.clone()
1030    }
1031
1032    pub fn left_sidebar(&self) -> &ViewHandle<Sidebar> {
1033        &self.left_sidebar
1034    }
1035
1036    pub fn right_sidebar(&self) -> &ViewHandle<Sidebar> {
1037        &self.right_sidebar
1038    }
1039
1040    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
1041        &self.status_bar
1042    }
1043
1044    pub fn user_store(&self) -> &ModelHandle<UserStore> {
1045        &self.user_store
1046    }
1047
1048    pub fn project(&self) -> &ModelHandle<Project> {
1049        &self.project
1050    }
1051
1052    pub fn client(&self) -> &Arc<Client> {
1053        &self.client
1054    }
1055
1056    pub fn set_titlebar_item(
1057        &mut self,
1058        item: impl Into<AnyViewHandle>,
1059        cx: &mut ViewContext<Self>,
1060    ) {
1061        self.titlebar_item = Some(item.into());
1062        cx.notify();
1063    }
1064
1065    /// Call the given callback with a workspace whose project is local.
1066    ///
1067    /// If the given workspace has a local project, then it will be passed
1068    /// to the callback. Otherwise, a new empty window will be created.
1069    pub fn with_local_workspace<T, F>(
1070        &mut self,
1071        cx: &mut ViewContext<Self>,
1072        app_state: Arc<AppState>,
1073        callback: F,
1074    ) -> T
1075    where
1076        T: 'static,
1077        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1078    {
1079        if self.project.read(cx).is_local() {
1080            callback(self, cx)
1081        } else {
1082            let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
1083                let mut workspace = Workspace::new(
1084                    Project::local(
1085                        app_state.client.clone(),
1086                        app_state.user_store.clone(),
1087                        app_state.project_store.clone(),
1088                        app_state.languages.clone(),
1089                        app_state.fs.clone(),
1090                        cx,
1091                    ),
1092                    app_state.default_item_factory,
1093                    cx,
1094                );
1095                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
1096                workspace
1097            });
1098            workspace.update(cx, callback)
1099        }
1100    }
1101
1102    pub fn worktrees<'a>(
1103        &self,
1104        cx: &'a AppContext,
1105    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1106        self.project.read(cx).worktrees(cx)
1107    }
1108
1109    pub fn visible_worktrees<'a>(
1110        &self,
1111        cx: &'a AppContext,
1112    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1113        self.project.read(cx).visible_worktrees(cx)
1114    }
1115
1116    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1117        let futures = self
1118            .worktrees(cx)
1119            .filter_map(|worktree| worktree.read(cx).as_local())
1120            .map(|worktree| worktree.scan_complete())
1121            .collect::<Vec<_>>();
1122        async move {
1123            for future in futures {
1124                future.await;
1125            }
1126        }
1127    }
1128
1129    pub fn close(
1130        &mut self,
1131        _: &CloseWindow,
1132        cx: &mut ViewContext<Self>,
1133    ) -> Option<Task<Result<()>>> {
1134        let prepare = self.prepare_to_close(cx);
1135        Some(cx.spawn(|this, mut cx| async move {
1136            if prepare.await? {
1137                this.update(&mut cx, |_, cx| {
1138                    let window_id = cx.window_id();
1139                    cx.remove_window(window_id);
1140                });
1141            }
1142            Ok(())
1143        }))
1144    }
1145
1146    pub fn prepare_to_close(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
1147        self.save_all_internal(true, cx)
1148    }
1149
1150    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1151        let save_all = self.save_all_internal(false, cx);
1152        Some(cx.foreground().spawn(async move {
1153            save_all.await?;
1154            Ok(())
1155        }))
1156    }
1157
1158    fn save_all_internal(
1159        &mut self,
1160        should_prompt_to_save: bool,
1161        cx: &mut ViewContext<Self>,
1162    ) -> Task<Result<bool>> {
1163        if self.project.read(cx).is_read_only() {
1164            return Task::ready(Ok(true));
1165        }
1166
1167        let dirty_items = self
1168            .panes
1169            .iter()
1170            .flat_map(|pane| {
1171                pane.read(cx).items().filter_map(|item| {
1172                    if item.is_dirty(cx) {
1173                        Some((pane.clone(), item.boxed_clone()))
1174                    } else {
1175                        None
1176                    }
1177                })
1178            })
1179            .collect::<Vec<_>>();
1180
1181        let project = self.project.clone();
1182        cx.spawn_weak(|_, mut cx| async move {
1183            for (pane, item) in dirty_items {
1184                let (singleton, project_entry_ids) =
1185                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1186                if singleton || !project_entry_ids.is_empty() {
1187                    if let Some(ix) =
1188                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))
1189                    {
1190                        if !Pane::save_item(
1191                            project.clone(),
1192                            &pane,
1193                            ix,
1194                            &*item,
1195                            should_prompt_to_save,
1196                            &mut cx,
1197                        )
1198                        .await?
1199                        {
1200                            return Ok(false);
1201                        }
1202                    }
1203                }
1204            }
1205            Ok(true)
1206        })
1207    }
1208
1209    #[allow(clippy::type_complexity)]
1210    pub fn open_paths(
1211        &mut self,
1212        mut abs_paths: Vec<PathBuf>,
1213        visible: bool,
1214        cx: &mut ViewContext<Self>,
1215    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
1216        let fs = self.fs.clone();
1217
1218        // Sort the paths to ensure we add worktrees for parents before their children.
1219        abs_paths.sort_unstable();
1220        cx.spawn(|this, mut cx| async move {
1221            let mut project_paths = Vec::new();
1222            for path in &abs_paths {
1223                project_paths.push(
1224                    this.update(&mut cx, |this, cx| {
1225                        this.project_path_for_path(path, visible, cx)
1226                    })
1227                    .await
1228                    .log_err(),
1229                );
1230            }
1231
1232            let tasks = abs_paths
1233                .iter()
1234                .cloned()
1235                .zip(project_paths.into_iter())
1236                .map(|(abs_path, project_path)| {
1237                    let this = this.clone();
1238                    cx.spawn(|mut cx| {
1239                        let fs = fs.clone();
1240                        async move {
1241                            let (_worktree, project_path) = project_path?;
1242                            if fs.is_file(&abs_path).await {
1243                                Some(
1244                                    this.update(&mut cx, |this, cx| {
1245                                        this.open_path(project_path, true, cx)
1246                                    })
1247                                    .await,
1248                                )
1249                            } else {
1250                                None
1251                            }
1252                        }
1253                    })
1254                })
1255                .collect::<Vec<_>>();
1256
1257            futures::future::join_all(tasks).await
1258        })
1259    }
1260
1261    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1262        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1263            files: false,
1264            directories: true,
1265            multiple: true,
1266        });
1267        cx.spawn(|this, mut cx| async move {
1268            if let Some(paths) = paths.recv().await.flatten() {
1269                let results = this
1270                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
1271                    .await;
1272                for result in results.into_iter().flatten() {
1273                    result.log_err();
1274                }
1275            }
1276        })
1277        .detach();
1278    }
1279
1280    fn remove_folder_from_project(
1281        &mut self,
1282        RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
1283        cx: &mut ViewContext<Self>,
1284    ) {
1285        self.project
1286            .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1287    }
1288
1289    fn project_path_for_path(
1290        &self,
1291        abs_path: &Path,
1292        visible: bool,
1293        cx: &mut ViewContext<Self>,
1294    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1295        let entry = self.project().update(cx, |project, cx| {
1296            project.find_or_create_local_worktree(abs_path, visible, cx)
1297        });
1298        cx.spawn(|_, cx| async move {
1299            let (worktree, path) = entry.await?;
1300            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1301            Ok((
1302                worktree,
1303                ProjectPath {
1304                    worktree_id,
1305                    path: path.into(),
1306                },
1307            ))
1308        })
1309    }
1310
1311    /// Returns the modal that was toggled closed if it was open.
1312    pub fn toggle_modal<V, F>(
1313        &mut self,
1314        cx: &mut ViewContext<Self>,
1315        add_view: F,
1316    ) -> Option<ViewHandle<V>>
1317    where
1318        V: 'static + View,
1319        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1320    {
1321        cx.notify();
1322        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1323        // it. Otherwise, create a new modal and set it as active.
1324        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1325        if let Some(already_open_modal) = already_open_modal {
1326            cx.focus_self();
1327            Some(already_open_modal)
1328        } else {
1329            let modal = add_view(self, cx);
1330            cx.focus(&modal);
1331            self.modal = Some(modal.into());
1332            None
1333        }
1334    }
1335
1336    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1337        self.modal
1338            .as_ref()
1339            .and_then(|modal| modal.clone().downcast::<V>())
1340    }
1341
1342    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1343        if self.modal.take().is_some() {
1344            cx.focus(&self.active_pane);
1345            cx.notify();
1346        }
1347    }
1348
1349    pub fn show_notification<V: Notification>(
1350        &mut self,
1351        id: usize,
1352        cx: &mut ViewContext<Self>,
1353        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1354    ) {
1355        let type_id = TypeId::of::<V>();
1356        if self
1357            .notifications
1358            .iter()
1359            .all(|(existing_type_id, existing_id, _)| {
1360                (*existing_type_id, *existing_id) != (type_id, id)
1361            })
1362        {
1363            let notification = build_notification(cx);
1364            cx.subscribe(&notification, move |this, handle, event, cx| {
1365                if handle.read(cx).should_dismiss_notification_on_event(event) {
1366                    this.dismiss_notification(type_id, id, cx);
1367                }
1368            })
1369            .detach();
1370            self.notifications
1371                .push((type_id, id, Box::new(notification)));
1372            cx.notify();
1373        }
1374    }
1375
1376    fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1377        self.notifications
1378            .retain(|(existing_type_id, existing_id, _)| {
1379                if (*existing_type_id, *existing_id) == (type_id, id) {
1380                    cx.notify();
1381                    false
1382                } else {
1383                    true
1384                }
1385            });
1386    }
1387
1388    pub fn items<'a>(
1389        &'a self,
1390        cx: &'a AppContext,
1391    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1392        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1393    }
1394
1395    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1396        self.items_of_type(cx).max_by_key(|item| item.id())
1397    }
1398
1399    pub fn items_of_type<'a, T: Item>(
1400        &'a self,
1401        cx: &'a AppContext,
1402    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1403        self.panes
1404            .iter()
1405            .flat_map(|pane| pane.read(cx).items_of_type())
1406    }
1407
1408    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1409        self.active_pane().read(cx).active_item()
1410    }
1411
1412    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1413        self.active_item(cx).and_then(|item| item.project_path(cx))
1414    }
1415
1416    pub fn save_active_item(
1417        &mut self,
1418        force_name_change: bool,
1419        cx: &mut ViewContext<Self>,
1420    ) -> Task<Result<()>> {
1421        let project = self.project.clone();
1422        if let Some(item) = self.active_item(cx) {
1423            if !force_name_change && item.can_save(cx) {
1424                if item.has_conflict(cx.as_ref()) {
1425                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1426
1427                    let mut answer = cx.prompt(
1428                        PromptLevel::Warning,
1429                        CONFLICT_MESSAGE,
1430                        &["Overwrite", "Cancel"],
1431                    );
1432                    cx.spawn(|_, mut cx| async move {
1433                        let answer = answer.recv().await;
1434                        if answer == Some(0) {
1435                            cx.update(|cx| item.save(project, cx)).await?;
1436                        }
1437                        Ok(())
1438                    })
1439                } else {
1440                    item.save(project, cx)
1441                }
1442            } else if item.is_singleton(cx) {
1443                let worktree = self.worktrees(cx).next();
1444                let start_abs_path = worktree
1445                    .and_then(|w| w.read(cx).as_local())
1446                    .map_or(Path::new(""), |w| w.abs_path())
1447                    .to_path_buf();
1448                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1449                cx.spawn(|_, mut cx| async move {
1450                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1451                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1452                    }
1453                    Ok(())
1454                })
1455            } else {
1456                Task::ready(Ok(()))
1457            }
1458        } else {
1459            Task::ready(Ok(()))
1460        }
1461    }
1462
1463    pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext<Self>) {
1464        let sidebar = match sidebar_side {
1465            SidebarSide::Left => &mut self.left_sidebar,
1466            SidebarSide::Right => &mut self.right_sidebar,
1467        };
1468        let open = sidebar.update(cx, |sidebar, cx| {
1469            let open = !sidebar.is_open();
1470            sidebar.set_open(open, cx);
1471            open
1472        });
1473
1474        if open {
1475            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1476        }
1477
1478        cx.focus_self();
1479        cx.notify();
1480    }
1481
1482    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1483        let sidebar = match action.sidebar_side {
1484            SidebarSide::Left => &mut self.left_sidebar,
1485            SidebarSide::Right => &mut self.right_sidebar,
1486        };
1487        let active_item = sidebar.update(cx, move |sidebar, cx| {
1488            if sidebar.is_open() && sidebar.active_item_ix() == action.item_index {
1489                sidebar.set_open(false, cx);
1490                None
1491            } else {
1492                sidebar.set_open(true, cx);
1493                sidebar.activate_item(action.item_index, cx);
1494                sidebar.active_item().cloned()
1495            }
1496        });
1497
1498        if let Some(active_item) = active_item {
1499            Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx);
1500
1501            if active_item.is_focused(cx) {
1502                cx.focus_self();
1503            } else {
1504                cx.focus(active_item.to_any());
1505            }
1506        } else {
1507            cx.focus_self();
1508        }
1509        cx.notify();
1510    }
1511
1512    pub fn toggle_sidebar_item_focus(
1513        &mut self,
1514        sidebar_side: SidebarSide,
1515        item_index: usize,
1516        cx: &mut ViewContext<Self>,
1517    ) {
1518        let sidebar = match sidebar_side {
1519            SidebarSide::Left => &mut self.left_sidebar,
1520            SidebarSide::Right => &mut self.right_sidebar,
1521        };
1522        let active_item = sidebar.update(cx, |sidebar, cx| {
1523            sidebar.set_open(true, cx);
1524            sidebar.activate_item(item_index, cx);
1525            sidebar.active_item().cloned()
1526        });
1527        if let Some(active_item) = active_item {
1528            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1529
1530            if active_item.is_focused(cx) {
1531                cx.focus_self();
1532            } else {
1533                cx.focus(active_item.to_any());
1534            }
1535        }
1536        cx.notify();
1537    }
1538
1539    pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1540        cx.focus_self();
1541        cx.notify();
1542    }
1543
1544    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1545        let pane = cx.add_view(|cx| Pane::new(None, cx));
1546        let pane_id = pane.id();
1547        cx.subscribe(&pane, move |this, _, event, cx| {
1548            this.handle_pane_event(pane_id, event, cx)
1549        })
1550        .detach();
1551        self.panes.push(pane.clone());
1552        cx.focus(pane.clone());
1553        cx.emit(Event::PaneAdded(pane.clone()));
1554        pane
1555    }
1556
1557    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1558        let active_pane = self.active_pane().clone();
1559        Pane::add_item(self, &active_pane, item, true, true, None, cx);
1560    }
1561
1562    pub fn open_path(
1563        &mut self,
1564        path: impl Into<ProjectPath>,
1565        focus_item: bool,
1566        cx: &mut ViewContext<Self>,
1567    ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1568        let pane = self.active_pane().downgrade();
1569        let task = self.load_path(path.into(), cx);
1570        cx.spawn(|this, mut cx| async move {
1571            let (project_entry_id, build_item) = task.await?;
1572            let pane = pane
1573                .upgrade(&cx)
1574                .ok_or_else(|| anyhow!("pane was closed"))?;
1575            this.update(&mut cx, |this, cx| {
1576                Ok(Pane::open_item(
1577                    this,
1578                    pane,
1579                    project_entry_id,
1580                    focus_item,
1581                    cx,
1582                    build_item,
1583                ))
1584            })
1585        })
1586    }
1587
1588    pub(crate) fn load_path(
1589        &mut self,
1590        path: ProjectPath,
1591        cx: &mut ViewContext<Self>,
1592    ) -> Task<
1593        Result<(
1594            ProjectEntryId,
1595            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1596        )>,
1597    > {
1598        let project = self.project().clone();
1599        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1600        cx.as_mut().spawn(|mut cx| async move {
1601            let (project_entry_id, project_item) = project_item.await?;
1602            let build_item = cx.update(|cx| {
1603                cx.default_global::<ProjectItemBuilders>()
1604                    .get(&project_item.model_type())
1605                    .ok_or_else(|| anyhow!("no item builder for project item"))
1606                    .cloned()
1607            })?;
1608            let build_item =
1609                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1610            Ok((project_entry_id, build_item))
1611        })
1612    }
1613
1614    pub fn open_project_item<T>(
1615        &mut self,
1616        project_item: ModelHandle<T::Item>,
1617        cx: &mut ViewContext<Self>,
1618    ) -> ViewHandle<T>
1619    where
1620        T: ProjectItem,
1621    {
1622        use project::Item as _;
1623
1624        let entry_id = project_item.read(cx).entry_id(cx);
1625        if let Some(item) = entry_id
1626            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1627            .and_then(|item| item.downcast())
1628        {
1629            self.activate_item(&item, cx);
1630            return item;
1631        }
1632
1633        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1634        self.add_item(Box::new(item.clone()), cx);
1635        item
1636    }
1637
1638    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1639        let result = self.panes.iter().find_map(|pane| {
1640            pane.read(cx)
1641                .index_for_item(item)
1642                .map(|ix| (pane.clone(), ix))
1643        });
1644        if let Some((pane, ix)) = result {
1645            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1646            true
1647        } else {
1648            false
1649        }
1650    }
1651
1652    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1653        let panes = self.center.panes();
1654        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1655            cx.focus(pane);
1656        } else {
1657            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1658        }
1659    }
1660
1661    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1662        let next_pane = {
1663            let panes = self.center.panes();
1664            let ix = panes
1665                .iter()
1666                .position(|pane| **pane == self.active_pane)
1667                .unwrap();
1668            let next_ix = (ix + 1) % panes.len();
1669            panes[next_ix].clone()
1670        };
1671        cx.focus(next_pane);
1672    }
1673
1674    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1675        let prev_pane = {
1676            let panes = self.center.panes();
1677            let ix = panes
1678                .iter()
1679                .position(|pane| **pane == self.active_pane)
1680                .unwrap();
1681            let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1682            panes[prev_ix].clone()
1683        };
1684        cx.focus(prev_pane);
1685    }
1686
1687    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1688        if self.active_pane != pane {
1689            self.active_pane
1690                .update(cx, |pane, cx| pane.set_active(false, cx));
1691            self.active_pane = pane.clone();
1692            self.active_pane
1693                .update(cx, |pane, cx| pane.set_active(true, cx));
1694            self.status_bar.update(cx, |status_bar, cx| {
1695                status_bar.set_active_pane(&self.active_pane, cx);
1696            });
1697            self.active_item_path_changed(cx);
1698
1699            if &pane == self.dock_pane() {
1700                Dock::show(self, cx);
1701            } else {
1702                self.last_active_center_pane = Some(pane.clone());
1703                if self.dock.is_anchored_at(DockAnchor::Expanded) {
1704                    Dock::hide(self, cx);
1705                }
1706            }
1707            cx.notify();
1708        }
1709
1710        self.update_followers(
1711            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1712                id: self.active_item(cx).map(|item| item.id() as u64),
1713                leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1714            }),
1715            cx,
1716        );
1717    }
1718
1719    fn handle_pane_event(
1720        &mut self,
1721        pane_id: usize,
1722        event: &pane::Event,
1723        cx: &mut ViewContext<Self>,
1724    ) {
1725        if let Some(pane) = self.pane(pane_id) {
1726            let is_dock = &pane == self.dock.pane();
1727            match event {
1728                pane::Event::Split(direction) if !is_dock => {
1729                    self.split_pane(pane, *direction, cx);
1730                }
1731                pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
1732                pane::Event::Remove if is_dock => Dock::hide(self, cx),
1733                pane::Event::Focused => self.handle_pane_focused(pane, cx),
1734                pane::Event::ActivateItem { local } => {
1735                    if *local {
1736                        self.unfollow(&pane, cx);
1737                    }
1738                    if &pane == self.active_pane() {
1739                        self.active_item_path_changed(cx);
1740                    }
1741                }
1742                pane::Event::ChangeItemTitle => {
1743                    if pane == self.active_pane {
1744                        self.active_item_path_changed(cx);
1745                    }
1746                    self.update_window_edited(cx);
1747                }
1748                pane::Event::RemoveItem { item_id } => {
1749                    self.update_window_edited(cx);
1750                    if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1751                        if entry.get().id() == pane.id() {
1752                            entry.remove();
1753                        }
1754                    }
1755                }
1756                _ => {}
1757            }
1758        } else if self.dock.visible_pane().is_none() {
1759            error!("pane {} not found", pane_id);
1760        }
1761    }
1762
1763    pub fn split_pane(
1764        &mut self,
1765        pane: ViewHandle<Pane>,
1766        direction: SplitDirection,
1767        cx: &mut ViewContext<Self>,
1768    ) -> Option<ViewHandle<Pane>> {
1769        if &pane == self.dock_pane() {
1770            warn!("Can't split dock pane.");
1771            return None;
1772        }
1773
1774        pane.read(cx).active_item().map(|item| {
1775            let new_pane = self.add_pane(cx);
1776            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1777                Pane::add_item(self, &new_pane, clone, true, true, None, cx);
1778            }
1779            self.center.split(&pane, &new_pane, direction).unwrap();
1780            cx.notify();
1781            new_pane
1782        })
1783    }
1784
1785    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1786        if self.center.remove(&pane).unwrap() {
1787            self.panes.retain(|p| p != &pane);
1788            cx.focus(self.panes.last().unwrap().clone());
1789            self.unfollow(&pane, cx);
1790            self.last_leaders_by_pane.remove(&pane.downgrade());
1791            for removed_item in pane.read(cx).items() {
1792                self.panes_by_item.remove(&removed_item.id());
1793            }
1794            if self.last_active_center_pane == Some(pane) {
1795                self.last_active_center_pane = None;
1796            }
1797
1798            cx.notify();
1799        } else {
1800            self.active_item_path_changed(cx);
1801        }
1802    }
1803
1804    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1805        &self.panes
1806    }
1807
1808    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1809        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1810    }
1811
1812    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1813        &self.active_pane
1814    }
1815
1816    pub fn dock_pane(&self) -> &ViewHandle<Pane> {
1817        self.dock.pane()
1818    }
1819
1820    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1821        if let Some(remote_id) = remote_id {
1822            self.remote_entity_subscription =
1823                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1824        } else {
1825            self.remote_entity_subscription.take();
1826        }
1827    }
1828
1829    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1830        self.leader_state.followers.remove(&peer_id);
1831        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1832            for state in states_by_pane.into_values() {
1833                for item in state.items_by_leader_view_id.into_values() {
1834                    if let FollowerItem::Loaded(item) = item {
1835                        item.set_leader_replica_id(None, cx);
1836                    }
1837                }
1838            }
1839        }
1840        cx.notify();
1841    }
1842
1843    pub fn toggle_follow(
1844        &mut self,
1845        ToggleFollow(leader_id): &ToggleFollow,
1846        cx: &mut ViewContext<Self>,
1847    ) -> Option<Task<Result<()>>> {
1848        let leader_id = *leader_id;
1849        let pane = self.active_pane().clone();
1850
1851        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1852            if leader_id == prev_leader_id {
1853                return None;
1854            }
1855        }
1856
1857        self.last_leaders_by_pane
1858            .insert(pane.downgrade(), leader_id);
1859        self.follower_states_by_leader
1860            .entry(leader_id)
1861            .or_default()
1862            .insert(pane.clone(), Default::default());
1863        cx.notify();
1864
1865        let project_id = self.project.read(cx).remote_id()?;
1866        let request = self.client.request(proto::Follow {
1867            project_id,
1868            leader_id: leader_id.0,
1869        });
1870        Some(cx.spawn_weak(|this, mut cx| async move {
1871            let response = request.await?;
1872            if let Some(this) = this.upgrade(&cx) {
1873                this.update(&mut cx, |this, _| {
1874                    let state = this
1875                        .follower_states_by_leader
1876                        .get_mut(&leader_id)
1877                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1878                        .ok_or_else(|| anyhow!("following interrupted"))?;
1879                    state.active_view_id = response.active_view_id;
1880                    Ok::<_, anyhow::Error>(())
1881                })?;
1882                Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
1883                    .await?;
1884            }
1885            Ok(())
1886        }))
1887    }
1888
1889    pub fn follow_next_collaborator(
1890        &mut self,
1891        _: &FollowNextCollaborator,
1892        cx: &mut ViewContext<Self>,
1893    ) -> Option<Task<Result<()>>> {
1894        let collaborators = self.project.read(cx).collaborators();
1895        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1896            let mut collaborators = collaborators.keys().copied();
1897            for peer_id in collaborators.by_ref() {
1898                if peer_id == leader_id {
1899                    break;
1900                }
1901            }
1902            collaborators.next()
1903        } else if let Some(last_leader_id) =
1904            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1905        {
1906            if collaborators.contains_key(last_leader_id) {
1907                Some(*last_leader_id)
1908            } else {
1909                None
1910            }
1911        } else {
1912            None
1913        };
1914
1915        next_leader_id
1916            .or_else(|| collaborators.keys().copied().next())
1917            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1918    }
1919
1920    pub fn unfollow(
1921        &mut self,
1922        pane: &ViewHandle<Pane>,
1923        cx: &mut ViewContext<Self>,
1924    ) -> Option<PeerId> {
1925        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1926            let leader_id = *leader_id;
1927            if let Some(state) = states_by_pane.remove(pane) {
1928                for (_, item) in state.items_by_leader_view_id {
1929                    if let FollowerItem::Loaded(item) = item {
1930                        item.set_leader_replica_id(None, cx);
1931                    }
1932                }
1933
1934                if states_by_pane.is_empty() {
1935                    self.follower_states_by_leader.remove(&leader_id);
1936                    if let Some(project_id) = self.project.read(cx).remote_id() {
1937                        self.client
1938                            .send(proto::Unfollow {
1939                                project_id,
1940                                leader_id: leader_id.0,
1941                            })
1942                            .log_err();
1943                    }
1944                }
1945
1946                cx.notify();
1947                return Some(leader_id);
1948            }
1949        }
1950        None
1951    }
1952
1953    pub fn is_following(&self, peer_id: PeerId) -> bool {
1954        self.follower_states_by_leader.contains_key(&peer_id)
1955    }
1956
1957    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1958        let project = &self.project.read(cx);
1959        let mut worktree_root_names = String::new();
1960        for (i, name) in project.worktree_root_names(cx).enumerate() {
1961            if i > 0 {
1962                worktree_root_names.push_str(", ");
1963            }
1964            worktree_root_names.push_str(name);
1965        }
1966
1967        // TODO: There should be a better system in place for this
1968        // (https://github.com/zed-industries/zed/issues/1290)
1969        let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
1970        let container_theme = if is_fullscreen {
1971            let mut container_theme = theme.workspace.titlebar.container;
1972            container_theme.padding.left = container_theme.padding.right;
1973            container_theme
1974        } else {
1975            theme.workspace.titlebar.container
1976        };
1977
1978        enum TitleBar {}
1979        ConstrainedBox::new(
1980            MouseEventHandler::<TitleBar>::new(0, cx, |_, _| {
1981                Container::new(
1982                    Stack::new()
1983                        .with_child(
1984                            Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
1985                                .aligned()
1986                                .left()
1987                                .boxed(),
1988                        )
1989                        .with_children(
1990                            self.titlebar_item
1991                                .as_ref()
1992                                .map(|item| ChildView::new(item).aligned().right().boxed()),
1993                        )
1994                        .boxed(),
1995                )
1996                .with_style(container_theme)
1997                .boxed()
1998            })
1999            .on_click(MouseButton::Left, |event, cx| {
2000                if event.click_count == 2 {
2001                    cx.zoom_window(cx.window_id());
2002                }
2003            })
2004            .boxed(),
2005        )
2006        .with_height(theme.workspace.titlebar.height)
2007        .named("titlebar")
2008    }
2009
2010    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2011        let active_entry = self.active_project_path(cx);
2012        self.project
2013            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2014        self.update_window_title(cx);
2015    }
2016
2017    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2018        let mut title = String::new();
2019        let project = self.project().read(cx);
2020        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2021            let filename = path
2022                .path
2023                .file_name()
2024                .map(|s| s.to_string_lossy())
2025                .or_else(|| {
2026                    Some(Cow::Borrowed(
2027                        project
2028                            .worktree_for_id(path.worktree_id, cx)?
2029                            .read(cx)
2030                            .root_name(),
2031                    ))
2032                });
2033            if let Some(filename) = filename {
2034                title.push_str(filename.as_ref());
2035                title.push_str("");
2036            }
2037        }
2038        for (i, name) in project.worktree_root_names(cx).enumerate() {
2039            if i > 0 {
2040                title.push_str(", ");
2041            }
2042            title.push_str(name);
2043        }
2044        if title.is_empty() {
2045            title = "empty project".to_string();
2046        }
2047        cx.set_window_title(&title);
2048    }
2049
2050    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2051        let is_edited = !self.project.read(cx).is_read_only()
2052            && self
2053                .items(cx)
2054                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2055        if is_edited != self.window_edited {
2056            self.window_edited = is_edited;
2057            cx.set_window_edited(self.window_edited)
2058        }
2059    }
2060
2061    fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
2062        if self.project.read(cx).is_read_only() {
2063            enum DisconnectedOverlay {}
2064            Some(
2065                MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
2066                    let theme = &cx.global::<Settings>().theme;
2067                    Label::new(
2068                        "Your connection to the remote project has been lost.".to_string(),
2069                        theme.workspace.disconnected_overlay.text.clone(),
2070                    )
2071                    .aligned()
2072                    .contained()
2073                    .with_style(theme.workspace.disconnected_overlay.container)
2074                    .boxed()
2075                })
2076                .with_cursor_style(CursorStyle::Arrow)
2077                .capture_all()
2078                .boxed(),
2079            )
2080        } else {
2081            None
2082        }
2083    }
2084
2085    fn render_notifications(&self, theme: &theme::Workspace) -> Option<ElementBox> {
2086        if self.notifications.is_empty() {
2087            None
2088        } else {
2089            Some(
2090                Flex::column()
2091                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2092                        ChildView::new(notification.as_ref())
2093                            .contained()
2094                            .with_style(theme.notification)
2095                            .boxed()
2096                    }))
2097                    .constrained()
2098                    .with_width(theme.notifications.width)
2099                    .contained()
2100                    .with_style(theme.notifications.container)
2101                    .aligned()
2102                    .bottom()
2103                    .right()
2104                    .boxed(),
2105            )
2106        }
2107    }
2108
2109    // RPC handlers
2110
2111    async fn handle_follow(
2112        this: ViewHandle<Self>,
2113        envelope: TypedEnvelope<proto::Follow>,
2114        _: Arc<Client>,
2115        mut cx: AsyncAppContext,
2116    ) -> Result<proto::FollowResponse> {
2117        this.update(&mut cx, |this, cx| {
2118            this.leader_state
2119                .followers
2120                .insert(envelope.original_sender_id()?);
2121
2122            let active_view_id = this
2123                .active_item(cx)
2124                .and_then(|i| i.to_followable_item_handle(cx))
2125                .map(|i| i.id() as u64);
2126            Ok(proto::FollowResponse {
2127                active_view_id,
2128                views: this
2129                    .panes()
2130                    .iter()
2131                    .flat_map(|pane| {
2132                        let leader_id = this.leader_for_pane(pane).map(|id| id.0);
2133                        pane.read(cx).items().filter_map({
2134                            let cx = &cx;
2135                            move |item| {
2136                                let id = item.id() as u64;
2137                                let item = item.to_followable_item_handle(cx)?;
2138                                let variant = item.to_state_proto(cx)?;
2139                                Some(proto::View {
2140                                    id,
2141                                    leader_id,
2142                                    variant: Some(variant),
2143                                })
2144                            }
2145                        })
2146                    })
2147                    .collect(),
2148            })
2149        })
2150    }
2151
2152    async fn handle_unfollow(
2153        this: ViewHandle<Self>,
2154        envelope: TypedEnvelope<proto::Unfollow>,
2155        _: Arc<Client>,
2156        mut cx: AsyncAppContext,
2157    ) -> Result<()> {
2158        this.update(&mut cx, |this, _| {
2159            this.leader_state
2160                .followers
2161                .remove(&envelope.original_sender_id()?);
2162            Ok(())
2163        })
2164    }
2165
2166    async fn handle_update_followers(
2167        this: ViewHandle<Self>,
2168        envelope: TypedEnvelope<proto::UpdateFollowers>,
2169        _: Arc<Client>,
2170        mut cx: AsyncAppContext,
2171    ) -> Result<()> {
2172        let leader_id = envelope.original_sender_id()?;
2173        match envelope
2174            .payload
2175            .variant
2176            .ok_or_else(|| anyhow!("invalid update"))?
2177        {
2178            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2179                this.update(&mut cx, |this, cx| {
2180                    this.update_leader_state(leader_id, cx, |state, _| {
2181                        state.active_view_id = update_active_view.id;
2182                    });
2183                    Ok::<_, anyhow::Error>(())
2184                })
2185            }
2186            proto::update_followers::Variant::UpdateView(update_view) => {
2187                this.update(&mut cx, |this, cx| {
2188                    let variant = update_view
2189                        .variant
2190                        .ok_or_else(|| anyhow!("missing update view variant"))?;
2191                    this.update_leader_state(leader_id, cx, |state, cx| {
2192                        let variant = variant.clone();
2193                        match state
2194                            .items_by_leader_view_id
2195                            .entry(update_view.id)
2196                            .or_insert(FollowerItem::Loading(Vec::new()))
2197                        {
2198                            FollowerItem::Loaded(item) => {
2199                                item.apply_update_proto(variant, cx).log_err();
2200                            }
2201                            FollowerItem::Loading(updates) => updates.push(variant),
2202                        }
2203                    });
2204                    Ok(())
2205                })
2206            }
2207            proto::update_followers::Variant::CreateView(view) => {
2208                let panes = this.read_with(&cx, |this, _| {
2209                    this.follower_states_by_leader
2210                        .get(&leader_id)
2211                        .into_iter()
2212                        .flat_map(|states_by_pane| states_by_pane.keys())
2213                        .cloned()
2214                        .collect()
2215                });
2216                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2217                    .await?;
2218                Ok(())
2219            }
2220        }
2221        .log_err();
2222
2223        Ok(())
2224    }
2225
2226    async fn add_views_from_leader(
2227        this: ViewHandle<Self>,
2228        leader_id: PeerId,
2229        panes: Vec<ViewHandle<Pane>>,
2230        views: Vec<proto::View>,
2231        cx: &mut AsyncAppContext,
2232    ) -> Result<()> {
2233        let project = this.read_with(cx, |this, _| this.project.clone());
2234        let replica_id = project
2235            .read_with(cx, |project, _| {
2236                project
2237                    .collaborators()
2238                    .get(&leader_id)
2239                    .map(|c| c.replica_id)
2240            })
2241            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2242
2243        let item_builders = cx.update(|cx| {
2244            cx.default_global::<FollowableItemBuilders>()
2245                .values()
2246                .map(|b| b.0)
2247                .collect::<Vec<_>>()
2248        });
2249
2250        let mut item_tasks_by_pane = HashMap::default();
2251        for pane in panes {
2252            let mut item_tasks = Vec::new();
2253            let mut leader_view_ids = Vec::new();
2254            for view in &views {
2255                let mut variant = view.variant.clone();
2256                if variant.is_none() {
2257                    Err(anyhow!("missing variant"))?;
2258                }
2259                for build_item in &item_builders {
2260                    let task =
2261                        cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2262                    if let Some(task) = task {
2263                        item_tasks.push(task);
2264                        leader_view_ids.push(view.id);
2265                        break;
2266                    } else {
2267                        assert!(variant.is_some());
2268                    }
2269                }
2270            }
2271
2272            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2273        }
2274
2275        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2276            let items = futures::future::try_join_all(item_tasks).await?;
2277            this.update(cx, |this, cx| {
2278                let state = this
2279                    .follower_states_by_leader
2280                    .get_mut(&leader_id)?
2281                    .get_mut(&pane)?;
2282
2283                for (id, item) in leader_view_ids.into_iter().zip(items) {
2284                    item.set_leader_replica_id(Some(replica_id), cx);
2285                    match state.items_by_leader_view_id.entry(id) {
2286                        hash_map::Entry::Occupied(e) => {
2287                            let e = e.into_mut();
2288                            if let FollowerItem::Loading(updates) = e {
2289                                for update in updates.drain(..) {
2290                                    item.apply_update_proto(update, cx)
2291                                        .context("failed to apply view update")
2292                                        .log_err();
2293                                }
2294                            }
2295                            *e = FollowerItem::Loaded(item);
2296                        }
2297                        hash_map::Entry::Vacant(e) => {
2298                            e.insert(FollowerItem::Loaded(item));
2299                        }
2300                    }
2301                }
2302
2303                Some(())
2304            });
2305        }
2306        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2307
2308        Ok(())
2309    }
2310
2311    fn update_followers(
2312        &self,
2313        update: proto::update_followers::Variant,
2314        cx: &AppContext,
2315    ) -> Option<()> {
2316        let project_id = self.project.read(cx).remote_id()?;
2317        if !self.leader_state.followers.is_empty() {
2318            self.client
2319                .send(proto::UpdateFollowers {
2320                    project_id,
2321                    follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2322                    variant: Some(update),
2323                })
2324                .log_err();
2325        }
2326        None
2327    }
2328
2329    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2330        self.follower_states_by_leader
2331            .iter()
2332            .find_map(|(leader_id, state)| {
2333                if state.contains_key(pane) {
2334                    Some(*leader_id)
2335                } else {
2336                    None
2337                }
2338            })
2339    }
2340
2341    fn update_leader_state(
2342        &mut self,
2343        leader_id: PeerId,
2344        cx: &mut ViewContext<Self>,
2345        mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2346    ) {
2347        for (_, state) in self
2348            .follower_states_by_leader
2349            .get_mut(&leader_id)
2350            .into_iter()
2351            .flatten()
2352        {
2353            update_fn(state, cx);
2354        }
2355        self.leader_updated(leader_id, cx);
2356    }
2357
2358    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2359        let mut items_to_add = Vec::new();
2360        for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2361            if let Some(FollowerItem::Loaded(item)) = state
2362                .active_view_id
2363                .and_then(|id| state.items_by_leader_view_id.get(&id))
2364            {
2365                items_to_add.push((pane.clone(), item.boxed_clone()));
2366            }
2367        }
2368
2369        for (pane, item) in items_to_add {
2370            Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2371            if pane == self.active_pane {
2372                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2373            }
2374            cx.notify();
2375        }
2376        None
2377    }
2378
2379    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2380        if !active {
2381            for pane in &self.panes {
2382                pane.update(cx, |pane, cx| {
2383                    if let Some(item) = pane.active_item() {
2384                        item.workspace_deactivated(cx);
2385                    }
2386                    if matches!(
2387                        cx.global::<Settings>().autosave,
2388                        Autosave::OnWindowChange | Autosave::OnFocusChange
2389                    ) {
2390                        for item in pane.items() {
2391                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2392                                .detach_and_log_err(cx);
2393                        }
2394                    }
2395                });
2396            }
2397        }
2398    }
2399}
2400
2401impl Entity for Workspace {
2402    type Event = Event;
2403}
2404
2405impl View for Workspace {
2406    fn ui_name() -> &'static str {
2407        "Workspace"
2408    }
2409
2410    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2411        let theme = cx.global::<Settings>().theme.clone();
2412        Stack::new()
2413            .with_child(
2414                Flex::column()
2415                    .with_child(self.render_titlebar(&theme, cx))
2416                    .with_child(
2417                        Stack::new()
2418                            .with_child({
2419                                let project = self.project.clone();
2420                                Flex::row()
2421                                    .with_children(
2422                                        if self.left_sidebar.read(cx).active_item().is_some() {
2423                                            Some(
2424                                                ChildView::new(&self.left_sidebar)
2425                                                    .flex(0.8, false)
2426                                                    .boxed(),
2427                                            )
2428                                        } else {
2429                                            None
2430                                        },
2431                                    )
2432                                    .with_child(
2433                                        FlexItem::new(
2434                                            Flex::column()
2435                                                .with_child(
2436                                                    FlexItem::new(self.center.render(
2437                                                        &project,
2438                                                        &theme,
2439                                                        &self.follower_states_by_leader,
2440                                                        cx,
2441                                                    ))
2442                                                    .flex(1., true)
2443                                                    .boxed(),
2444                                                )
2445                                                .with_children(self.dock.render(
2446                                                    &theme,
2447                                                    DockAnchor::Bottom,
2448                                                    cx,
2449                                                ))
2450                                                .boxed(),
2451                                        )
2452                                        .flex(1., true)
2453                                        .boxed(),
2454                                    )
2455                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2456                                    .with_children(
2457                                        if self.right_sidebar.read(cx).active_item().is_some() {
2458                                            Some(
2459                                                ChildView::new(&self.right_sidebar)
2460                                                    .flex(0.8, false)
2461                                                    .boxed(),
2462                                            )
2463                                        } else {
2464                                            None
2465                                        },
2466                                    )
2467                                    .boxed()
2468                            })
2469                            .with_child(
2470                                Overlay::new(
2471                                    Stack::new()
2472                                        .with_children(self.dock.render(
2473                                            &theme,
2474                                            DockAnchor::Expanded,
2475                                            cx,
2476                                        ))
2477                                        .with_children(self.modal.as_ref().map(|m| {
2478                                            ChildView::new(m)
2479                                                .contained()
2480                                                .with_style(theme.workspace.modal)
2481                                                .aligned()
2482                                                .top()
2483                                                .boxed()
2484                                        }))
2485                                        .with_children(self.render_notifications(&theme.workspace))
2486                                        .boxed(),
2487                                )
2488                                .boxed(),
2489                            )
2490                            .flex(1.0, true)
2491                            .boxed(),
2492                    )
2493                    .with_child(ChildView::new(&self.status_bar).boxed())
2494                    .contained()
2495                    .with_background_color(theme.workspace.background)
2496                    .boxed(),
2497            )
2498            .with_children(DragAndDrop::render(cx))
2499            .with_children(self.render_disconnected_overlay(cx))
2500            .named("workspace")
2501    }
2502
2503    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
2504        if cx.is_self_focused() {
2505            cx.focus(&self.active_pane);
2506        }
2507    }
2508
2509    fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context {
2510        let mut keymap = Self::default_keymap_context();
2511        if self.active_pane() == self.dock_pane() {
2512            keymap.set.insert("Dock".into());
2513        }
2514        keymap
2515    }
2516}
2517
2518pub trait WorkspaceHandle {
2519    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2520}
2521
2522impl WorkspaceHandle for ViewHandle<Workspace> {
2523    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2524        self.read(cx)
2525            .worktrees(cx)
2526            .flat_map(|worktree| {
2527                let worktree_id = worktree.read(cx).id();
2528                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2529                    worktree_id,
2530                    path: f.path.clone(),
2531                })
2532            })
2533            .collect::<Vec<_>>()
2534    }
2535}
2536
2537impl std::fmt::Debug for OpenPaths {
2538    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2539        f.debug_struct("OpenPaths")
2540            .field("paths", &self.paths)
2541            .finish()
2542    }
2543}
2544
2545fn open(_: &Open, cx: &mut MutableAppContext) {
2546    let mut paths = cx.prompt_for_paths(PathPromptOptions {
2547        files: true,
2548        directories: true,
2549        multiple: true,
2550    });
2551    cx.spawn(|mut cx| async move {
2552        if let Some(paths) = paths.recv().await.flatten() {
2553            cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2554        }
2555    })
2556    .detach();
2557}
2558
2559pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2560
2561pub fn activate_workspace_for_project(
2562    cx: &mut MutableAppContext,
2563    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2564) -> Option<ViewHandle<Workspace>> {
2565    for window_id in cx.window_ids().collect::<Vec<_>>() {
2566        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2567            let project = workspace_handle.read(cx).project.clone();
2568            if project.update(cx, &predicate) {
2569                cx.activate_window(window_id);
2570                return Some(workspace_handle);
2571            }
2572        }
2573    }
2574    None
2575}
2576
2577#[allow(clippy::type_complexity)]
2578pub fn open_paths(
2579    abs_paths: &[PathBuf],
2580    app_state: &Arc<AppState>,
2581    cx: &mut MutableAppContext,
2582) -> Task<(
2583    ViewHandle<Workspace>,
2584    Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
2585)> {
2586    log::info!("open paths {:?}", abs_paths);
2587
2588    // Open paths in existing workspace if possible
2589    let existing =
2590        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2591
2592    let app_state = app_state.clone();
2593    let abs_paths = abs_paths.to_vec();
2594    cx.spawn(|mut cx| async move {
2595        let mut new_project = None;
2596        let workspace = if let Some(existing) = existing {
2597            existing
2598        } else {
2599            let contains_directory =
2600                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2601                    .await
2602                    .contains(&false);
2603
2604            cx.add_window((app_state.build_window_options)(), |cx| {
2605                let project = Project::local(
2606                    app_state.client.clone(),
2607                    app_state.user_store.clone(),
2608                    app_state.project_store.clone(),
2609                    app_state.languages.clone(),
2610                    app_state.fs.clone(),
2611                    cx,
2612                );
2613                new_project = Some(project.clone());
2614                let mut workspace = Workspace::new(project, app_state.default_item_factory, cx);
2615                (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2616                if contains_directory {
2617                    workspace.toggle_sidebar(SidebarSide::Left, cx);
2618                }
2619                workspace
2620            })
2621            .1
2622        };
2623
2624        let items = workspace
2625            .update(&mut cx, |workspace, cx| {
2626                workspace.open_paths(abs_paths, true, cx)
2627            })
2628            .await;
2629
2630        (workspace, items)
2631    })
2632}
2633
2634fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2635    let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2636        let mut workspace = Workspace::new(
2637            Project::local(
2638                app_state.client.clone(),
2639                app_state.user_store.clone(),
2640                app_state.project_store.clone(),
2641                app_state.languages.clone(),
2642                app_state.fs.clone(),
2643                cx,
2644            ),
2645            app_state.default_item_factory,
2646            cx,
2647        );
2648        (app_state.initialize_workspace)(&mut workspace, app_state, cx);
2649        workspace
2650    });
2651    cx.dispatch_action_at(window_id, workspace.id(), NewFile);
2652}
2653
2654#[cfg(test)]
2655mod tests {
2656    use std::cell::Cell;
2657
2658    use crate::sidebar::SidebarItem;
2659
2660    use super::*;
2661    use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
2662    use project::{FakeFs, Project, ProjectEntryId};
2663    use serde_json::json;
2664
2665    pub fn default_item_factory(
2666        _workspace: &mut Workspace,
2667        _cx: &mut ViewContext<Workspace>,
2668    ) -> Box<dyn ItemHandle> {
2669        unimplemented!();
2670    }
2671
2672    #[gpui::test]
2673    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
2674        cx.foreground().forbid_parking();
2675        Settings::test_async(cx);
2676
2677        let fs = FakeFs::new(cx.background());
2678        let project = Project::test(fs, [], cx).await;
2679        let (_, workspace) =
2680            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
2681
2682        // Adding an item with no ambiguity renders the tab without detail.
2683        let item1 = cx.add_view(&workspace, |_| {
2684            let mut item = TestItem::new();
2685            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
2686            item
2687        });
2688        workspace.update(cx, |workspace, cx| {
2689            workspace.add_item(Box::new(item1.clone()), cx);
2690        });
2691        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
2692
2693        // Adding an item that creates ambiguity increases the level of detail on
2694        // both tabs.
2695        let item2 = cx.add_view(&workspace, |_| {
2696            let mut item = TestItem::new();
2697            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2698            item
2699        });
2700        workspace.update(cx, |workspace, cx| {
2701            workspace.add_item(Box::new(item2.clone()), cx);
2702        });
2703        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2704        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2705
2706        // Adding an item that creates ambiguity increases the level of detail only
2707        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
2708        // we stop at the highest detail available.
2709        let item3 = cx.add_view(&workspace, |_| {
2710            let mut item = TestItem::new();
2711            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2712            item
2713        });
2714        workspace.update(cx, |workspace, cx| {
2715            workspace.add_item(Box::new(item3.clone()), cx);
2716        });
2717        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2718        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2719        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2720    }
2721
2722    #[gpui::test]
2723    async fn test_tracking_active_path(cx: &mut TestAppContext) {
2724        cx.foreground().forbid_parking();
2725        Settings::test_async(cx);
2726        let fs = FakeFs::new(cx.background());
2727        fs.insert_tree(
2728            "/root1",
2729            json!({
2730                "one.txt": "",
2731                "two.txt": "",
2732            }),
2733        )
2734        .await;
2735        fs.insert_tree(
2736            "/root2",
2737            json!({
2738                "three.txt": "",
2739            }),
2740        )
2741        .await;
2742
2743        let project = Project::test(fs, ["root1".as_ref()], cx).await;
2744        let (window_id, workspace) =
2745            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
2746        let worktree_id = project.read_with(cx, |project, cx| {
2747            project.worktrees(cx).next().unwrap().read(cx).id()
2748        });
2749
2750        let item1 = cx.add_view(&workspace, |_| {
2751            let mut item = TestItem::new();
2752            item.project_path = Some((worktree_id, "one.txt").into());
2753            item
2754        });
2755        let item2 = cx.add_view(&workspace, |_| {
2756            let mut item = TestItem::new();
2757            item.project_path = Some((worktree_id, "two.txt").into());
2758            item
2759        });
2760
2761        // Add an item to an empty pane
2762        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
2763        project.read_with(cx, |project, cx| {
2764            assert_eq!(
2765                project.active_entry(),
2766                project
2767                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2768                    .map(|e| e.id)
2769            );
2770        });
2771        assert_eq!(
2772            cx.current_window_title(window_id).as_deref(),
2773            Some("one.txt — root1")
2774        );
2775
2776        // Add a second item to a non-empty pane
2777        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
2778        assert_eq!(
2779            cx.current_window_title(window_id).as_deref(),
2780            Some("two.txt — root1")
2781        );
2782        project.read_with(cx, |project, cx| {
2783            assert_eq!(
2784                project.active_entry(),
2785                project
2786                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
2787                    .map(|e| e.id)
2788            );
2789        });
2790
2791        // Close the active item
2792        workspace
2793            .update(cx, |workspace, cx| {
2794                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
2795            })
2796            .await
2797            .unwrap();
2798        assert_eq!(
2799            cx.current_window_title(window_id).as_deref(),
2800            Some("one.txt — root1")
2801        );
2802        project.read_with(cx, |project, cx| {
2803            assert_eq!(
2804                project.active_entry(),
2805                project
2806                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2807                    .map(|e| e.id)
2808            );
2809        });
2810
2811        // Add a project folder
2812        project
2813            .update(cx, |project, cx| {
2814                project.find_or_create_local_worktree("/root2", true, cx)
2815            })
2816            .await
2817            .unwrap();
2818        assert_eq!(
2819            cx.current_window_title(window_id).as_deref(),
2820            Some("one.txt — root1, root2")
2821        );
2822
2823        // Remove a project folder
2824        project.update(cx, |project, cx| {
2825            project.remove_worktree(worktree_id, cx);
2826        });
2827        assert_eq!(
2828            cx.current_window_title(window_id).as_deref(),
2829            Some("one.txt — root2")
2830        );
2831    }
2832
2833    #[gpui::test]
2834    async fn test_close_window(cx: &mut TestAppContext) {
2835        cx.foreground().forbid_parking();
2836        Settings::test_async(cx);
2837        let fs = FakeFs::new(cx.background());
2838        fs.insert_tree("/root", json!({ "one": "" })).await;
2839
2840        let project = Project::test(fs, ["root".as_ref()], cx).await;
2841        let (window_id, workspace) =
2842            cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
2843
2844        // When there are no dirty items, there's nothing to do.
2845        let item1 = cx.add_view(&workspace, |_| TestItem::new());
2846        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
2847        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2848        assert!(task.await.unwrap());
2849
2850        // When there are dirty untitled items, prompt to save each one. If the user
2851        // cancels any prompt, then abort.
2852        let item2 = cx.add_view(&workspace, |_| {
2853            let mut item = TestItem::new();
2854            item.is_dirty = true;
2855            item
2856        });
2857        let item3 = cx.add_view(&workspace, |_| {
2858            let mut item = TestItem::new();
2859            item.is_dirty = true;
2860            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2861            item
2862        });
2863        workspace.update(cx, |w, cx| {
2864            w.add_item(Box::new(item2.clone()), cx);
2865            w.add_item(Box::new(item3.clone()), cx);
2866        });
2867        let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
2868        cx.foreground().run_until_parked();
2869        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
2870        cx.foreground().run_until_parked();
2871        assert!(!cx.has_pending_prompt(window_id));
2872        assert!(!task.await.unwrap());
2873    }
2874
2875    #[gpui::test]
2876    async fn test_close_pane_items(cx: &mut TestAppContext) {
2877        cx.foreground().forbid_parking();
2878        Settings::test_async(cx);
2879        let fs = FakeFs::new(cx.background());
2880
2881        let project = Project::test(fs, None, cx).await;
2882        let (window_id, workspace) =
2883            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
2884
2885        let item1 = cx.add_view(&workspace, |_| {
2886            let mut item = TestItem::new();
2887            item.is_dirty = true;
2888            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
2889            item
2890        });
2891        let item2 = cx.add_view(&workspace, |_| {
2892            let mut item = TestItem::new();
2893            item.is_dirty = true;
2894            item.has_conflict = true;
2895            item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
2896            item
2897        });
2898        let item3 = cx.add_view(&workspace, |_| {
2899            let mut item = TestItem::new();
2900            item.is_dirty = true;
2901            item.has_conflict = true;
2902            item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
2903            item
2904        });
2905        let item4 = cx.add_view(&workspace, |_| {
2906            let mut item = TestItem::new();
2907            item.is_dirty = true;
2908            item
2909        });
2910        let pane = workspace.update(cx, |workspace, cx| {
2911            workspace.add_item(Box::new(item1.clone()), cx);
2912            workspace.add_item(Box::new(item2.clone()), cx);
2913            workspace.add_item(Box::new(item3.clone()), cx);
2914            workspace.add_item(Box::new(item4.clone()), cx);
2915            workspace.active_pane().clone()
2916        });
2917
2918        let close_items = workspace.update(cx, |workspace, cx| {
2919            pane.update(cx, |pane, cx| {
2920                pane.activate_item(1, true, true, cx);
2921                assert_eq!(pane.active_item().unwrap().id(), item2.id());
2922            });
2923
2924            let item1_id = item1.id();
2925            let item3_id = item3.id();
2926            let item4_id = item4.id();
2927            Pane::close_items(workspace, pane.clone(), cx, move |id| {
2928                [item1_id, item3_id, item4_id].contains(&id)
2929            })
2930        });
2931
2932        cx.foreground().run_until_parked();
2933        pane.read_with(cx, |pane, _| {
2934            assert_eq!(pane.items().count(), 4);
2935            assert_eq!(pane.active_item().unwrap().id(), item1.id());
2936        });
2937
2938        cx.simulate_prompt_answer(window_id, 0);
2939        cx.foreground().run_until_parked();
2940        pane.read_with(cx, |pane, cx| {
2941            assert_eq!(item1.read(cx).save_count, 1);
2942            assert_eq!(item1.read(cx).save_as_count, 0);
2943            assert_eq!(item1.read(cx).reload_count, 0);
2944            assert_eq!(pane.items().count(), 3);
2945            assert_eq!(pane.active_item().unwrap().id(), item3.id());
2946        });
2947
2948        cx.simulate_prompt_answer(window_id, 1);
2949        cx.foreground().run_until_parked();
2950        pane.read_with(cx, |pane, cx| {
2951            assert_eq!(item3.read(cx).save_count, 0);
2952            assert_eq!(item3.read(cx).save_as_count, 0);
2953            assert_eq!(item3.read(cx).reload_count, 1);
2954            assert_eq!(pane.items().count(), 2);
2955            assert_eq!(pane.active_item().unwrap().id(), item4.id());
2956        });
2957
2958        cx.simulate_prompt_answer(window_id, 0);
2959        cx.foreground().run_until_parked();
2960        cx.simulate_new_path_selection(|_| Some(Default::default()));
2961        close_items.await.unwrap();
2962        pane.read_with(cx, |pane, cx| {
2963            assert_eq!(item4.read(cx).save_count, 0);
2964            assert_eq!(item4.read(cx).save_as_count, 1);
2965            assert_eq!(item4.read(cx).reload_count, 0);
2966            assert_eq!(pane.items().count(), 1);
2967            assert_eq!(pane.active_item().unwrap().id(), item2.id());
2968        });
2969    }
2970
2971    #[gpui::test]
2972    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
2973        cx.foreground().forbid_parking();
2974        Settings::test_async(cx);
2975        let fs = FakeFs::new(cx.background());
2976
2977        let project = Project::test(fs, [], cx).await;
2978        let (window_id, workspace) =
2979            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
2980
2981        // Create several workspace items with single project entries, and two
2982        // workspace items with multiple project entries.
2983        let single_entry_items = (0..=4)
2984            .map(|project_entry_id| {
2985                let mut item = TestItem::new();
2986                item.is_dirty = true;
2987                item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
2988                item.is_singleton = true;
2989                item
2990            })
2991            .collect::<Vec<_>>();
2992        let item_2_3 = {
2993            let mut item = TestItem::new();
2994            item.is_dirty = true;
2995            item.is_singleton = false;
2996            item.project_entry_ids =
2997                vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
2998            item
2999        };
3000        let item_3_4 = {
3001            let mut item = TestItem::new();
3002            item.is_dirty = true;
3003            item.is_singleton = false;
3004            item.project_entry_ids =
3005                vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
3006            item
3007        };
3008
3009        // Create two panes that contain the following project entries:
3010        //   left pane:
3011        //     multi-entry items:   (2, 3)
3012        //     single-entry items:  0, 1, 2, 3, 4
3013        //   right pane:
3014        //     single-entry items:  1
3015        //     multi-entry items:   (3, 4)
3016        let left_pane = workspace.update(cx, |workspace, cx| {
3017            let left_pane = workspace.active_pane().clone();
3018            workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
3019            for item in &single_entry_items {
3020                workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
3021            }
3022            left_pane.update(cx, |pane, cx| {
3023                pane.activate_item(2, true, true, cx);
3024            });
3025
3026            workspace
3027                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3028                .unwrap();
3029
3030            left_pane
3031        });
3032
3033        //Need to cause an effect flush in order to respect new focus
3034        workspace.update(cx, |workspace, cx| {
3035            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
3036            cx.focus(left_pane.clone());
3037        });
3038
3039        // When closing all of the items in the left pane, we should be prompted twice:
3040        // once for project entry 0, and once for project entry 2. After those two
3041        // prompts, the task should complete.
3042
3043        let close = workspace.update(cx, |workspace, cx| {
3044            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3045        });
3046
3047        cx.foreground().run_until_parked();
3048        left_pane.read_with(cx, |pane, cx| {
3049            assert_eq!(
3050                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3051                &[ProjectEntryId::from_proto(0)]
3052            );
3053        });
3054        cx.simulate_prompt_answer(window_id, 0);
3055
3056        cx.foreground().run_until_parked();
3057        left_pane.read_with(cx, |pane, cx| {
3058            assert_eq!(
3059                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3060                &[ProjectEntryId::from_proto(2)]
3061            );
3062        });
3063        cx.simulate_prompt_answer(window_id, 0);
3064
3065        cx.foreground().run_until_parked();
3066        close.await.unwrap();
3067        left_pane.read_with(cx, |pane, _| {
3068            assert_eq!(pane.items().count(), 0);
3069        });
3070    }
3071
3072    #[gpui::test]
3073    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3074        deterministic.forbid_parking();
3075
3076        Settings::test_async(cx);
3077        let fs = FakeFs::new(cx.background());
3078
3079        let project = Project::test(fs, [], cx).await;
3080        let (window_id, workspace) =
3081            cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3082
3083        let item = cx.add_view(&workspace, |_| {
3084            let mut item = TestItem::new();
3085            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3086            item
3087        });
3088        let item_id = item.id();
3089        workspace.update(cx, |workspace, cx| {
3090            workspace.add_item(Box::new(item.clone()), cx);
3091        });
3092
3093        // Autosave on window change.
3094        item.update(cx, |item, cx| {
3095            cx.update_global(|settings: &mut Settings, _| {
3096                settings.autosave = Autosave::OnWindowChange;
3097            });
3098            item.is_dirty = true;
3099        });
3100
3101        // Deactivating the window saves the file.
3102        cx.simulate_window_activation(None);
3103        deterministic.run_until_parked();
3104        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3105
3106        // Autosave on focus change.
3107        item.update(cx, |item, cx| {
3108            cx.focus_self();
3109            cx.update_global(|settings: &mut Settings, _| {
3110                settings.autosave = Autosave::OnFocusChange;
3111            });
3112            item.is_dirty = true;
3113        });
3114
3115        // Blurring the item saves the file.
3116        item.update(cx, |_, cx| cx.blur());
3117        deterministic.run_until_parked();
3118        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3119
3120        // Deactivating the window still saves the file.
3121        cx.simulate_window_activation(Some(window_id));
3122        item.update(cx, |item, cx| {
3123            cx.focus_self();
3124            item.is_dirty = true;
3125        });
3126        cx.simulate_window_activation(None);
3127
3128        deterministic.run_until_parked();
3129        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3130
3131        // Autosave after delay.
3132        item.update(cx, |item, cx| {
3133            cx.update_global(|settings: &mut Settings, _| {
3134                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3135            });
3136            item.is_dirty = true;
3137            cx.emit(TestItemEvent::Edit);
3138        });
3139
3140        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3141        deterministic.advance_clock(Duration::from_millis(250));
3142        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3143
3144        // After delay expires, the file is saved.
3145        deterministic.advance_clock(Duration::from_millis(250));
3146        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3147
3148        // Autosave on focus change, ensuring closing the tab counts as such.
3149        item.update(cx, |item, cx| {
3150            cx.update_global(|settings: &mut Settings, _| {
3151                settings.autosave = Autosave::OnFocusChange;
3152            });
3153            item.is_dirty = true;
3154        });
3155
3156        workspace
3157            .update(cx, |workspace, cx| {
3158                let pane = workspace.active_pane().clone();
3159                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3160            })
3161            .await
3162            .unwrap();
3163        assert!(!cx.has_pending_prompt(window_id));
3164        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3165
3166        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3167        workspace.update(cx, |workspace, cx| {
3168            workspace.add_item(Box::new(item.clone()), cx);
3169        });
3170        item.update(cx, |item, cx| {
3171            item.project_entry_ids = Default::default();
3172            item.is_dirty = true;
3173            cx.blur();
3174        });
3175        deterministic.run_until_parked();
3176        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3177
3178        // Ensure autosave is prevented for deleted files also when closing the buffer.
3179        let _close_items = workspace.update(cx, |workspace, cx| {
3180            let pane = workspace.active_pane().clone();
3181            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3182        });
3183        deterministic.run_until_parked();
3184        assert!(cx.has_pending_prompt(window_id));
3185        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3186    }
3187
3188    #[gpui::test]
3189    async fn test_pane_navigation(
3190        deterministic: Arc<Deterministic>,
3191        cx: &mut gpui::TestAppContext,
3192    ) {
3193        deterministic.forbid_parking();
3194        Settings::test_async(cx);
3195        let fs = FakeFs::new(cx.background());
3196
3197        let project = Project::test(fs, [], cx).await;
3198        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3199
3200        let item = cx.add_view(&workspace, |_| {
3201            let mut item = TestItem::new();
3202            item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3203            item
3204        });
3205        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3206        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3207        let toolbar_notify_count = Rc::new(RefCell::new(0));
3208
3209        workspace.update(cx, |workspace, cx| {
3210            workspace.add_item(Box::new(item.clone()), cx);
3211            let toolbar_notification_count = toolbar_notify_count.clone();
3212            cx.observe(&toolbar, move |_, _, _| {
3213                *toolbar_notification_count.borrow_mut() += 1
3214            })
3215            .detach();
3216        });
3217
3218        pane.read_with(cx, |pane, _| {
3219            assert!(!pane.can_navigate_backward());
3220            assert!(!pane.can_navigate_forward());
3221        });
3222
3223        item.update(cx, |item, cx| {
3224            item.set_state("one".to_string(), cx);
3225        });
3226
3227        // Toolbar must be notified to re-render the navigation buttons
3228        assert_eq!(*toolbar_notify_count.borrow(), 1);
3229
3230        pane.read_with(cx, |pane, _| {
3231            assert!(pane.can_navigate_backward());
3232            assert!(!pane.can_navigate_forward());
3233        });
3234
3235        workspace
3236            .update(cx, |workspace, cx| {
3237                Pane::go_back(workspace, Some(pane.clone()), cx)
3238            })
3239            .await;
3240
3241        assert_eq!(*toolbar_notify_count.borrow(), 3);
3242        pane.read_with(cx, |pane, _| {
3243            assert!(!pane.can_navigate_backward());
3244            assert!(pane.can_navigate_forward());
3245        });
3246    }
3247
3248    pub struct TestItem {
3249        state: String,
3250        pub label: String,
3251        save_count: usize,
3252        save_as_count: usize,
3253        reload_count: usize,
3254        is_dirty: bool,
3255        is_singleton: bool,
3256        has_conflict: bool,
3257        project_entry_ids: Vec<ProjectEntryId>,
3258        project_path: Option<ProjectPath>,
3259        nav_history: Option<ItemNavHistory>,
3260        tab_descriptions: Option<Vec<&'static str>>,
3261        tab_detail: Cell<Option<usize>>,
3262    }
3263
3264    pub enum TestItemEvent {
3265        Edit,
3266    }
3267
3268    impl Clone for TestItem {
3269        fn clone(&self) -> Self {
3270            Self {
3271                state: self.state.clone(),
3272                label: self.label.clone(),
3273                save_count: self.save_count,
3274                save_as_count: self.save_as_count,
3275                reload_count: self.reload_count,
3276                is_dirty: self.is_dirty,
3277                is_singleton: self.is_singleton,
3278                has_conflict: self.has_conflict,
3279                project_entry_ids: self.project_entry_ids.clone(),
3280                project_path: self.project_path.clone(),
3281                nav_history: None,
3282                tab_descriptions: None,
3283                tab_detail: Default::default(),
3284            }
3285        }
3286    }
3287
3288    impl TestItem {
3289        pub fn new() -> Self {
3290            Self {
3291                state: String::new(),
3292                label: String::new(),
3293                save_count: 0,
3294                save_as_count: 0,
3295                reload_count: 0,
3296                is_dirty: false,
3297                has_conflict: false,
3298                project_entry_ids: Vec::new(),
3299                project_path: None,
3300                is_singleton: true,
3301                nav_history: None,
3302                tab_descriptions: None,
3303                tab_detail: Default::default(),
3304            }
3305        }
3306
3307        pub fn with_label(mut self, state: &str) -> Self {
3308            self.label = state.to_string();
3309            self
3310        }
3311
3312        pub fn with_singleton(mut self, singleton: bool) -> Self {
3313            self.is_singleton = singleton;
3314            self
3315        }
3316
3317        pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
3318            self.project_entry_ids.extend(
3319                project_entry_ids
3320                    .iter()
3321                    .copied()
3322                    .map(ProjectEntryId::from_proto),
3323            );
3324            self
3325        }
3326
3327        fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
3328            self.push_to_nav_history(cx);
3329            self.state = state;
3330        }
3331
3332        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
3333            if let Some(history) = &mut self.nav_history {
3334                history.push(Some(Box::new(self.state.clone())), cx);
3335            }
3336        }
3337    }
3338
3339    impl Entity for TestItem {
3340        type Event = TestItemEvent;
3341    }
3342
3343    impl View for TestItem {
3344        fn ui_name() -> &'static str {
3345            "TestItem"
3346        }
3347
3348        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
3349            Empty::new().boxed()
3350        }
3351    }
3352
3353    impl Item for TestItem {
3354        fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
3355            self.tab_descriptions.as_ref().and_then(|descriptions| {
3356                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
3357                Some(description.into())
3358            })
3359        }
3360
3361        fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
3362            self.tab_detail.set(detail);
3363            Empty::new().boxed()
3364        }
3365
3366        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
3367            self.project_path.clone()
3368        }
3369
3370        fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
3371            self.project_entry_ids.iter().copied().collect()
3372        }
3373
3374        fn is_singleton(&self, _: &AppContext) -> bool {
3375            self.is_singleton
3376        }
3377
3378        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
3379            self.nav_history = Some(history);
3380        }
3381
3382        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
3383            let state = *state.downcast::<String>().unwrap_or_default();
3384            if state != self.state {
3385                self.state = state;
3386                true
3387            } else {
3388                false
3389            }
3390        }
3391
3392        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3393            self.push_to_nav_history(cx);
3394        }
3395
3396        fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
3397        where
3398            Self: Sized,
3399        {
3400            Some(self.clone())
3401        }
3402
3403        fn is_dirty(&self, _: &AppContext) -> bool {
3404            self.is_dirty
3405        }
3406
3407        fn has_conflict(&self, _: &AppContext) -> bool {
3408            self.has_conflict
3409        }
3410
3411        fn can_save(&self, _: &AppContext) -> bool {
3412            !self.project_entry_ids.is_empty()
3413        }
3414
3415        fn save(
3416            &mut self,
3417            _: ModelHandle<Project>,
3418            _: &mut ViewContext<Self>,
3419        ) -> Task<anyhow::Result<()>> {
3420            self.save_count += 1;
3421            self.is_dirty = false;
3422            Task::ready(Ok(()))
3423        }
3424
3425        fn save_as(
3426            &mut self,
3427            _: ModelHandle<Project>,
3428            _: std::path::PathBuf,
3429            _: &mut ViewContext<Self>,
3430        ) -> Task<anyhow::Result<()>> {
3431            self.save_as_count += 1;
3432            self.is_dirty = false;
3433            Task::ready(Ok(()))
3434        }
3435
3436        fn reload(
3437            &mut self,
3438            _: ModelHandle<Project>,
3439            _: &mut ViewContext<Self>,
3440        ) -> Task<anyhow::Result<()>> {
3441            self.reload_count += 1;
3442            self.is_dirty = false;
3443            Task::ready(Ok(()))
3444        }
3445
3446        fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
3447            vec![ItemEvent::UpdateTab, ItemEvent::Edit]
3448        }
3449    }
3450
3451    impl SidebarItem for TestItem {}
3452}