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