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