workspace.rs

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