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