workspace.rs

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