workspace.rs

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