workspace.rs

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