workspace.rs

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