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