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