workspace.rs

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