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