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