workspace.rs

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