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) -> &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        let id = cx.window_ids().find(|&id| cx.window_is_active(id));
1114        if let Some(id) = id {
1115            //This can only get called when the window's project connection has been lost
1116            //so we don't need to prompt the user for anything and instead just close the window
1117            cx.remove_window(id);
1118        }
1119    }
1120
1121    pub fn close(
1122        &mut self,
1123        _: &CloseWindow,
1124        cx: &mut ViewContext<Self>,
1125    ) -> Option<Task<Result<()>>> {
1126        let window_id = cx.window_id();
1127        let prepare = self.prepare_to_close(false, cx);
1128        Some(cx.spawn(|_, mut cx| async move {
1129            if prepare.await? {
1130                cx.remove_window(window_id);
1131            }
1132            Ok(())
1133        }))
1134    }
1135
1136    pub fn prepare_to_close(
1137        &mut self,
1138        quitting: bool,
1139        cx: &mut ViewContext<Self>,
1140    ) -> Task<Result<bool>> {
1141        let active_call = self.active_call().cloned();
1142        let window_id = cx.window_id();
1143        let workspace_count = cx
1144            .window_ids()
1145            .collect::<Vec<_>>()
1146            .into_iter()
1147            .filter_map(|window_id| {
1148                cx.app_context()
1149                    .root_view(window_id)?
1150                    .clone()
1151                    .downcast::<Workspace>()
1152            })
1153            .count();
1154
1155        cx.spawn(|this, mut cx| async move {
1156            if let Some(active_call) = active_call {
1157                if !quitting
1158                    && workspace_count == 1
1159                    && active_call.read_with(&cx, |call, _| call.room().is_some())
1160                {
1161                    let answer = cx.prompt(
1162                        window_id,
1163                        PromptLevel::Warning,
1164                        "Do you want to leave the current call?",
1165                        &["Close window and hang up", "Cancel"],
1166                    );
1167
1168                    if let Some(mut answer) = answer {
1169                        if answer.next().await == Some(1) {
1170                            return anyhow::Ok(false);
1171                        } else {
1172                            active_call
1173                                .update(&mut cx, |call, cx| call.hang_up(cx))
1174                                .await
1175                                .log_err();
1176                        }
1177                    }
1178                }
1179            }
1180
1181            Ok(this
1182                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
1183                .await?)
1184        })
1185    }
1186
1187    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1188        let save_all = self.save_all_internal(false, cx);
1189        Some(cx.foreground().spawn(async move {
1190            save_all.await?;
1191            Ok(())
1192        }))
1193    }
1194
1195    fn save_all_internal(
1196        &mut self,
1197        should_prompt_to_save: bool,
1198        cx: &mut ViewContext<Self>,
1199    ) -> Task<Result<bool>> {
1200        if self.project.read(cx).is_read_only() {
1201            return Task::ready(Ok(true));
1202        }
1203
1204        let dirty_items = self
1205            .panes
1206            .iter()
1207            .flat_map(|pane| {
1208                pane.read(cx).items().filter_map(|item| {
1209                    if item.is_dirty(cx) {
1210                        Some((pane.downgrade(), item.boxed_clone()))
1211                    } else {
1212                        None
1213                    }
1214                })
1215            })
1216            .collect::<Vec<_>>();
1217
1218        let project = self.project.clone();
1219        cx.spawn(|_, mut cx| async move {
1220            for (pane, item) in dirty_items {
1221                let (singleton, project_entry_ids) =
1222                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1223                if singleton || !project_entry_ids.is_empty() {
1224                    if let Some(ix) =
1225                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1226                    {
1227                        if !Pane::save_item(
1228                            project.clone(),
1229                            &pane,
1230                            ix,
1231                            &*item,
1232                            should_prompt_to_save,
1233                            &mut cx,
1234                        )
1235                        .await?
1236                        {
1237                            return Ok(false);
1238                        }
1239                    }
1240                }
1241            }
1242            Ok(true)
1243        })
1244    }
1245
1246    #[allow(clippy::type_complexity)]
1247    pub fn open_paths(
1248        &mut self,
1249        mut abs_paths: Vec<PathBuf>,
1250        visible: bool,
1251        cx: &mut ViewContext<Self>,
1252    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1253        let fs = self.fs.clone();
1254
1255        // Sort the paths to ensure we add worktrees for parents before their children.
1256        abs_paths.sort_unstable();
1257        cx.spawn(|this, mut cx| async move {
1258            let mut project_paths = Vec::new();
1259            for path in &abs_paths {
1260                if let Some(project_path) = this
1261                    .update(&mut cx, |this, cx| {
1262                        Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
1263                    })
1264                    .log_err()
1265                {
1266                    project_paths.push(project_path.await.log_err());
1267                } else {
1268                    project_paths.push(None);
1269                }
1270            }
1271
1272            let tasks = abs_paths
1273                .iter()
1274                .cloned()
1275                .zip(project_paths.into_iter())
1276                .map(|(abs_path, project_path)| {
1277                    let this = this.clone();
1278                    cx.spawn(|mut cx| {
1279                        let fs = fs.clone();
1280                        async move {
1281                            let (_worktree, project_path) = project_path?;
1282                            if fs.is_file(&abs_path).await {
1283                                Some(
1284                                    this.update(&mut cx, |this, cx| {
1285                                        this.open_path(project_path, None, true, cx)
1286                                    })
1287                                    .log_err()?
1288                                    .await,
1289                                )
1290                            } else {
1291                                None
1292                            }
1293                        }
1294                    })
1295                })
1296                .collect::<Vec<_>>();
1297
1298            futures::future::join_all(tasks).await
1299        })
1300    }
1301
1302    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1303        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1304            files: false,
1305            directories: true,
1306            multiple: true,
1307        });
1308        cx.spawn(|this, mut cx| async move {
1309            if let Some(paths) = paths.recv().await.flatten() {
1310                let results = this
1311                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1312                    .await;
1313                for result in results.into_iter().flatten() {
1314                    result.log_err();
1315                }
1316            }
1317            anyhow::Ok(())
1318        })
1319        .detach_and_log_err(cx);
1320    }
1321
1322    fn remove_folder_from_project(
1323        &mut self,
1324        RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
1325        cx: &mut ViewContext<Self>,
1326    ) {
1327        self.project
1328            .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1329    }
1330
1331    fn project_path_for_path(
1332        project: ModelHandle<Project>,
1333        abs_path: &Path,
1334        visible: bool,
1335        cx: &mut AppContext,
1336    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1337        let entry = project.update(cx, |project, cx| {
1338            project.find_or_create_local_worktree(abs_path, visible, cx)
1339        });
1340        cx.spawn(|cx| async move {
1341            let (worktree, path) = entry.await?;
1342            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1343            Ok((
1344                worktree,
1345                ProjectPath {
1346                    worktree_id,
1347                    path: path.into(),
1348                },
1349            ))
1350        })
1351    }
1352
1353    /// Returns the modal that was toggled closed if it was open.
1354    pub fn toggle_modal<V, F>(
1355        &mut self,
1356        cx: &mut ViewContext<Self>,
1357        add_view: F,
1358    ) -> Option<ViewHandle<V>>
1359    where
1360        V: 'static + Modal,
1361        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1362    {
1363        cx.notify();
1364        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1365        // it. Otherwise, create a new modal and set it as active.
1366        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1367        if let Some(already_open_modal) = already_open_modal {
1368            cx.focus_self();
1369            Some(already_open_modal)
1370        } else {
1371            let modal = add_view(self, cx);
1372            cx.subscribe(&modal, |this, _, event, cx| {
1373                if V::dismiss_on_event(event) {
1374                    this.dismiss_modal(cx);
1375                }
1376            })
1377            .detach();
1378            cx.focus(&modal);
1379            self.modal = Some(modal.into_any());
1380            None
1381        }
1382    }
1383
1384    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1385        self.modal
1386            .as_ref()
1387            .and_then(|modal| modal.clone().downcast::<V>())
1388    }
1389
1390    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1391        if self.modal.take().is_some() {
1392            cx.focus(&self.active_pane);
1393            cx.notify();
1394        }
1395    }
1396
1397    pub fn items<'a>(
1398        &'a self,
1399        cx: &'a AppContext,
1400    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1401        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1402    }
1403
1404    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1405        self.items_of_type(cx).max_by_key(|item| item.id())
1406    }
1407
1408    pub fn items_of_type<'a, T: Item>(
1409        &'a self,
1410        cx: &'a AppContext,
1411    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1412        self.panes
1413            .iter()
1414            .flat_map(|pane| pane.read(cx).items_of_type())
1415    }
1416
1417    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1418        self.active_pane().read(cx).active_item()
1419    }
1420
1421    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1422        self.active_item(cx).and_then(|item| item.project_path(cx))
1423    }
1424
1425    pub fn save_active_item(
1426        &mut self,
1427        force_name_change: bool,
1428        cx: &mut ViewContext<Self>,
1429    ) -> Task<Result<()>> {
1430        let project = self.project.clone();
1431        if let Some(item) = self.active_item(cx) {
1432            if !force_name_change && item.can_save(cx) {
1433                if item.has_conflict(cx) {
1434                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1435
1436                    let mut answer = cx.prompt(
1437                        PromptLevel::Warning,
1438                        CONFLICT_MESSAGE,
1439                        &["Overwrite", "Cancel"],
1440                    );
1441                    cx.spawn(|this, mut cx| async move {
1442                        let answer = answer.recv().await;
1443                        if answer == Some(0) {
1444                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
1445                                .await?;
1446                        }
1447                        Ok(())
1448                    })
1449                } else {
1450                    item.save(self.project.clone(), cx)
1451                }
1452            } else if item.is_singleton(cx) {
1453                let worktree = self.worktrees(cx).next();
1454                let start_abs_path = worktree
1455                    .and_then(|w| w.read(cx).as_local())
1456                    .map_or(Path::new(""), |w| w.abs_path())
1457                    .to_path_buf();
1458                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1459                cx.spawn(|this, mut cx| async move {
1460                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1461                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
1462                            .await?;
1463                    }
1464                    Ok(())
1465                })
1466            } else {
1467                Task::ready(Ok(()))
1468            }
1469        } else {
1470            Task::ready(Ok(()))
1471        }
1472    }
1473
1474    pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext<Self>) {
1475        let sidebar = match sidebar_side {
1476            SidebarSide::Left => &mut self.left_sidebar,
1477            SidebarSide::Right => &mut self.right_sidebar,
1478        };
1479        let open = sidebar.update(cx, |sidebar, cx| {
1480            let open = !sidebar.is_open();
1481            sidebar.set_open(open, cx);
1482            open
1483        });
1484
1485        if open {
1486            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1487        }
1488
1489        self.serialize_workspace(cx);
1490
1491        cx.focus_self();
1492        cx.notify();
1493    }
1494
1495    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1496        let sidebar = match action.sidebar_side {
1497            SidebarSide::Left => &mut self.left_sidebar,
1498            SidebarSide::Right => &mut self.right_sidebar,
1499        };
1500        let active_item = sidebar.update(cx, move |sidebar, cx| {
1501            if sidebar.is_open() && sidebar.active_item_ix() == action.item_index {
1502                sidebar.set_open(false, cx);
1503                None
1504            } else {
1505                sidebar.set_open(true, cx);
1506                sidebar.activate_item(action.item_index, cx);
1507                sidebar.active_item().cloned()
1508            }
1509        });
1510
1511        if let Some(active_item) = active_item {
1512            Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx);
1513
1514            if active_item.is_focused(cx) {
1515                cx.focus_self();
1516            } else {
1517                cx.focus(active_item.as_any());
1518            }
1519        } else {
1520            cx.focus_self();
1521        }
1522
1523        self.serialize_workspace(cx);
1524
1525        cx.notify();
1526    }
1527
1528    pub fn toggle_sidebar_item_focus(
1529        &mut self,
1530        sidebar_side: SidebarSide,
1531        item_index: usize,
1532        cx: &mut ViewContext<Self>,
1533    ) {
1534        let sidebar = match sidebar_side {
1535            SidebarSide::Left => &mut self.left_sidebar,
1536            SidebarSide::Right => &mut self.right_sidebar,
1537        };
1538        let active_item = sidebar.update(cx, |sidebar, cx| {
1539            sidebar.set_open(true, cx);
1540            sidebar.activate_item(item_index, cx);
1541            sidebar.active_item().cloned()
1542        });
1543        if let Some(active_item) = active_item {
1544            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1545
1546            if active_item.is_focused(cx) {
1547                cx.focus_self();
1548            } else {
1549                cx.focus(active_item.as_any());
1550            }
1551        }
1552
1553        self.serialize_workspace(cx);
1554
1555        cx.notify();
1556    }
1557
1558    pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1559        cx.focus_self();
1560        cx.notify();
1561    }
1562
1563    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1564        let pane =
1565            cx.add_view(|cx| Pane::new(self.weak_handle().id(), None, self.background_actions, cx));
1566        let pane_id = pane.id();
1567        cx.subscribe(&pane, move |this, _, event, cx| {
1568            this.handle_pane_event(pane_id, event, cx)
1569        })
1570        .detach();
1571        self.panes.push(pane.clone());
1572        cx.focus(&pane);
1573        cx.emit(Event::PaneAdded(pane.clone()));
1574        pane
1575    }
1576
1577    pub fn add_item_to_center(
1578        &mut self,
1579        item: Box<dyn ItemHandle>,
1580        cx: &mut ViewContext<Self>,
1581    ) -> bool {
1582        if let Some(center_pane) = self.last_active_center_pane.clone() {
1583            if let Some(center_pane) = center_pane.upgrade(cx) {
1584                Pane::add_item(self, &center_pane, item, true, true, None, cx);
1585                true
1586            } else {
1587                false
1588            }
1589        } else {
1590            false
1591        }
1592    }
1593
1594    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1595        let active_pane = self.active_pane().clone();
1596        Pane::add_item(self, &active_pane, item, true, true, None, cx);
1597    }
1598
1599    pub fn open_path(
1600        &mut self,
1601        path: impl Into<ProjectPath>,
1602        pane: Option<WeakViewHandle<Pane>>,
1603        focus_item: bool,
1604        cx: &mut ViewContext<Self>,
1605    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1606        let pane = pane.unwrap_or_else(|| {
1607            if !self.dock_active() {
1608                self.active_pane().downgrade()
1609            } else {
1610                self.last_active_center_pane.clone().unwrap_or_else(|| {
1611                    self.panes
1612                        .first()
1613                        .expect("There must be an active pane")
1614                        .downgrade()
1615                })
1616            }
1617        });
1618
1619        let task = self.load_path(path.into(), cx);
1620        cx.spawn(|this, mut cx| async move {
1621            let (project_entry_id, build_item) = task.await?;
1622            let pane = pane
1623                .upgrade(&cx)
1624                .ok_or_else(|| anyhow!("pane was closed"))?;
1625            this.update(&mut cx, |this, cx| {
1626                Pane::open_item(this, pane, project_entry_id, focus_item, cx, build_item)
1627            })
1628        })
1629    }
1630
1631    pub(crate) fn load_path(
1632        &mut self,
1633        path: ProjectPath,
1634        cx: &mut ViewContext<Self>,
1635    ) -> Task<
1636        Result<(
1637            ProjectEntryId,
1638            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1639        )>,
1640    > {
1641        let project = self.project().clone();
1642        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1643        cx.spawn(|_, mut cx| async move {
1644            let (project_entry_id, project_item) = project_item.await?;
1645            let build_item = cx.update(|cx| {
1646                cx.default_global::<ProjectItemBuilders>()
1647                    .get(&project_item.model_type())
1648                    .ok_or_else(|| anyhow!("no item builder for project item"))
1649                    .cloned()
1650            })?;
1651            let build_item =
1652                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1653            Ok((project_entry_id, build_item))
1654        })
1655    }
1656
1657    pub fn open_project_item<T>(
1658        &mut self,
1659        project_item: ModelHandle<T::Item>,
1660        cx: &mut ViewContext<Self>,
1661    ) -> ViewHandle<T>
1662    where
1663        T: ProjectItem,
1664    {
1665        use project::Item as _;
1666
1667        let entry_id = project_item.read(cx).entry_id(cx);
1668        if let Some(item) = entry_id
1669            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1670            .and_then(|item| item.downcast())
1671        {
1672            self.activate_item(&item, cx);
1673            return item;
1674        }
1675
1676        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1677        self.add_item(Box::new(item.clone()), cx);
1678        item
1679    }
1680
1681    pub fn open_shared_screen(&mut self, action: &OpenSharedScreen, cx: &mut ViewContext<Self>) {
1682        if let Some(shared_screen) =
1683            self.shared_screen_for_peer(action.peer_id, &self.active_pane, cx)
1684        {
1685            let pane = self.active_pane.clone();
1686            Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
1687        }
1688    }
1689
1690    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1691        let result = self.panes.iter().find_map(|pane| {
1692            pane.read(cx)
1693                .index_for_item(item)
1694                .map(|ix| (pane.clone(), ix))
1695        });
1696        if let Some((pane, ix)) = result {
1697            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1698            true
1699        } else {
1700            false
1701        }
1702    }
1703
1704    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1705        let panes = self.center.panes();
1706        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1707            cx.focus(&pane);
1708        } else {
1709            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1710        }
1711    }
1712
1713    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1714        let panes = self.center.panes();
1715        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1716            let next_ix = (ix + 1) % panes.len();
1717            let next_pane = panes[next_ix].clone();
1718            cx.focus(&next_pane);
1719        }
1720    }
1721
1722    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1723        let panes = self.center.panes();
1724        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1725            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1726            let prev_pane = panes[prev_ix].clone();
1727            cx.focus(&prev_pane);
1728        }
1729    }
1730
1731    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1732        if self.active_pane != pane {
1733            self.active_pane
1734                .update(cx, |pane, cx| pane.set_active(false, cx));
1735            self.active_pane = pane.clone();
1736            self.active_pane
1737                .update(cx, |pane, cx| pane.set_active(true, cx));
1738            self.status_bar.update(cx, |status_bar, cx| {
1739                status_bar.set_active_pane(&self.active_pane, cx);
1740            });
1741            self.active_item_path_changed(cx);
1742
1743            if &pane == self.dock_pane() {
1744                Dock::show(self, true, cx);
1745            } else {
1746                self.last_active_center_pane = Some(pane.downgrade());
1747                if self.dock.is_anchored_at(DockAnchor::Expanded) {
1748                    Dock::hide(self, cx);
1749                }
1750            }
1751            cx.notify();
1752        }
1753
1754        self.update_followers(
1755            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1756                id: self.active_item(cx).and_then(|item| {
1757                    item.to_followable_item_handle(cx)?
1758                        .remote_id(&self.client, cx)
1759                        .map(|id| id.to_proto())
1760                }),
1761                leader_id: self.leader_for_pane(&pane),
1762            }),
1763            cx,
1764        );
1765    }
1766
1767    fn handle_pane_event(
1768        &mut self,
1769        pane_id: usize,
1770        event: &pane::Event,
1771        cx: &mut ViewContext<Self>,
1772    ) {
1773        if let Some(pane) = self.pane(pane_id) {
1774            let is_dock = &pane == self.dock.pane();
1775            match event {
1776                pane::Event::Split(direction) if !is_dock => {
1777                    self.split_pane(pane, *direction, cx);
1778                }
1779                pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
1780                pane::Event::Remove if is_dock => Dock::hide(self, cx),
1781                pane::Event::ActivateItem { local } => {
1782                    if *local {
1783                        self.unfollow(&pane, cx);
1784                    }
1785                    if &pane == self.active_pane() {
1786                        self.active_item_path_changed(cx);
1787                    }
1788                }
1789                pane::Event::ChangeItemTitle => {
1790                    if pane == self.active_pane {
1791                        self.active_item_path_changed(cx);
1792                    }
1793                    self.update_window_edited(cx);
1794                }
1795                pane::Event::RemoveItem { item_id } => {
1796                    self.update_window_edited(cx);
1797                    if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1798                        if entry.get().id() == pane.id() {
1799                            entry.remove();
1800                        }
1801                    }
1802                }
1803                _ => {}
1804            }
1805
1806            self.serialize_workspace(cx);
1807        } else if self.dock.visible_pane().is_none() {
1808            error!("pane {} not found", pane_id);
1809        }
1810    }
1811
1812    pub fn split_pane(
1813        &mut self,
1814        pane: ViewHandle<Pane>,
1815        direction: SplitDirection,
1816        cx: &mut ViewContext<Self>,
1817    ) -> Option<ViewHandle<Pane>> {
1818        if &pane == self.dock_pane() {
1819            warn!("Can't split dock pane.");
1820            return None;
1821        }
1822
1823        let item = pane.read(cx).active_item()?;
1824        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
1825            let new_pane = self.add_pane(cx);
1826            Pane::add_item(self, &new_pane, clone, true, true, None, cx);
1827            self.center.split(&pane, &new_pane, direction).unwrap();
1828            Some(new_pane)
1829        } else {
1830            None
1831        };
1832        cx.notify();
1833        maybe_pane_handle
1834    }
1835
1836    pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext<Self>) {
1837        let Some(pane_to_split) = action.pane_to_split.upgrade(cx) else { return; };
1838        let Some(from) = action.from.upgrade(cx) else { return; };
1839        if &pane_to_split == self.dock_pane() {
1840            warn!("Can't split dock pane.");
1841            return;
1842        }
1843
1844        let new_pane = self.add_pane(cx);
1845        Pane::move_item(
1846            self,
1847            from.clone(),
1848            new_pane.clone(),
1849            action.item_id_to_move,
1850            0,
1851            cx,
1852        );
1853        self.center
1854            .split(&pane_to_split, &new_pane, action.split_direction)
1855            .unwrap();
1856        cx.notify();
1857    }
1858
1859    pub fn split_pane_with_project_entry(
1860        &mut self,
1861        action: &SplitWithProjectEntry,
1862        cx: &mut ViewContext<Self>,
1863    ) -> Option<Task<Result<()>>> {
1864        let pane_to_split = action.pane_to_split.upgrade(cx)?;
1865        if &pane_to_split == self.dock_pane() {
1866            warn!("Can't split dock pane.");
1867            return None;
1868        }
1869
1870        let new_pane = self.add_pane(cx);
1871        self.center
1872            .split(&pane_to_split, &new_pane, action.split_direction)
1873            .unwrap();
1874
1875        let path = self
1876            .project
1877            .read(cx)
1878            .path_for_entry(action.project_entry, cx)?;
1879        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
1880        Some(cx.foreground().spawn(async move {
1881            task.await?;
1882            Ok(())
1883        }))
1884    }
1885
1886    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1887        if self.center.remove(&pane).unwrap() {
1888            self.panes.retain(|p| p != &pane);
1889            cx.focus(self.panes.last().unwrap());
1890            self.unfollow(&pane, cx);
1891            self.last_leaders_by_pane.remove(&pane.downgrade());
1892            for removed_item in pane.read(cx).items() {
1893                self.panes_by_item.remove(&removed_item.id());
1894            }
1895            if self.last_active_center_pane == Some(pane.downgrade()) {
1896                self.last_active_center_pane = None;
1897            }
1898
1899            cx.notify();
1900        } else {
1901            self.active_item_path_changed(cx);
1902        }
1903    }
1904
1905    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1906        &self.panes
1907    }
1908
1909    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1910        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1911    }
1912
1913    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1914        &self.active_pane
1915    }
1916
1917    pub fn dock_pane(&self) -> &ViewHandle<Pane> {
1918        self.dock.pane()
1919    }
1920
1921    fn dock_active(&self) -> bool {
1922        &self.active_pane == self.dock.pane()
1923    }
1924
1925    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1926        if let Some(remote_id) = remote_id {
1927            self.remote_entity_subscription =
1928                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1929        } else {
1930            self.remote_entity_subscription.take();
1931        }
1932    }
1933
1934    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1935        self.leader_state.followers.remove(&peer_id);
1936        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1937            for state in states_by_pane.into_values() {
1938                for item in state.items_by_leader_view_id.into_values() {
1939                    item.set_leader_replica_id(None, cx);
1940                }
1941            }
1942        }
1943        cx.notify();
1944    }
1945
1946    pub fn toggle_follow(
1947        &mut self,
1948        ToggleFollow(leader_id): &ToggleFollow,
1949        cx: &mut ViewContext<Self>,
1950    ) -> Option<Task<Result<()>>> {
1951        let leader_id = *leader_id;
1952        let pane = self.active_pane().clone();
1953
1954        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1955            if leader_id == prev_leader_id {
1956                return None;
1957            }
1958        }
1959
1960        self.last_leaders_by_pane
1961            .insert(pane.downgrade(), leader_id);
1962        self.follower_states_by_leader
1963            .entry(leader_id)
1964            .or_default()
1965            .insert(pane.clone(), Default::default());
1966        cx.notify();
1967
1968        let project_id = self.project.read(cx).remote_id()?;
1969        let request = self.client.request(proto::Follow {
1970            project_id,
1971            leader_id: Some(leader_id),
1972        });
1973
1974        Some(cx.spawn(|this, mut cx| async move {
1975            let response = request.await?;
1976            this.update(&mut cx, |this, _| {
1977                let state = this
1978                    .follower_states_by_leader
1979                    .get_mut(&leader_id)
1980                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1981                    .ok_or_else(|| anyhow!("following interrupted"))?;
1982                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
1983                    Some(ViewId::from_proto(active_view_id)?)
1984                } else {
1985                    None
1986                };
1987                Ok::<_, anyhow::Error>(())
1988            })??;
1989            Self::add_views_from_leader(
1990                this.clone(),
1991                leader_id,
1992                vec![pane],
1993                response.views,
1994                &mut cx,
1995            )
1996            .await?;
1997            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
1998            Ok(())
1999        }))
2000    }
2001
2002    pub fn follow_next_collaborator(
2003        &mut self,
2004        _: &FollowNextCollaborator,
2005        cx: &mut ViewContext<Self>,
2006    ) -> Option<Task<Result<()>>> {
2007        let collaborators = self.project.read(cx).collaborators();
2008        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2009            let mut collaborators = collaborators.keys().copied();
2010            for peer_id in collaborators.by_ref() {
2011                if peer_id == leader_id {
2012                    break;
2013                }
2014            }
2015            collaborators.next()
2016        } else if let Some(last_leader_id) =
2017            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2018        {
2019            if collaborators.contains_key(last_leader_id) {
2020                Some(*last_leader_id)
2021            } else {
2022                None
2023            }
2024        } else {
2025            None
2026        };
2027
2028        next_leader_id
2029            .or_else(|| collaborators.keys().copied().next())
2030            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
2031    }
2032
2033    pub fn unfollow(
2034        &mut self,
2035        pane: &ViewHandle<Pane>,
2036        cx: &mut ViewContext<Self>,
2037    ) -> Option<PeerId> {
2038        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2039            let leader_id = *leader_id;
2040            if let Some(state) = states_by_pane.remove(pane) {
2041                for (_, item) in state.items_by_leader_view_id {
2042                    item.set_leader_replica_id(None, cx);
2043                }
2044
2045                if states_by_pane.is_empty() {
2046                    self.follower_states_by_leader.remove(&leader_id);
2047                    if let Some(project_id) = self.project.read(cx).remote_id() {
2048                        self.client
2049                            .send(proto::Unfollow {
2050                                project_id,
2051                                leader_id: Some(leader_id),
2052                            })
2053                            .log_err();
2054                    }
2055                }
2056
2057                cx.notify();
2058                return Some(leader_id);
2059            }
2060        }
2061        None
2062    }
2063
2064    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2065        self.follower_states_by_leader.contains_key(&peer_id)
2066    }
2067
2068    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2069        self.leader_state.followers.contains(&peer_id)
2070    }
2071
2072    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2073        // TODO: There should be a better system in place for this
2074        // (https://github.com/zed-industries/zed/issues/1290)
2075        let is_fullscreen = cx.window_is_fullscreen();
2076        let container_theme = if is_fullscreen {
2077            let mut container_theme = theme.workspace.titlebar.container;
2078            container_theme.padding.left = container_theme.padding.right;
2079            container_theme
2080        } else {
2081            theme.workspace.titlebar.container
2082        };
2083
2084        enum TitleBar {}
2085        MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
2086            Stack::new()
2087                .with_children(
2088                    self.titlebar_item
2089                        .as_ref()
2090                        .map(|item| ChildView::new(item, cx)),
2091                )
2092                .contained()
2093                .with_style(container_theme)
2094        })
2095        .on_click(MouseButton::Left, |event, _, cx| {
2096            if event.click_count == 2 {
2097                cx.zoom_window();
2098            }
2099        })
2100        .constrained()
2101        .with_height(theme.workspace.titlebar.height)
2102        .into_any_named("titlebar")
2103    }
2104
2105    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2106        let active_entry = self.active_project_path(cx);
2107        self.project
2108            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2109        self.update_window_title(cx);
2110    }
2111
2112    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2113        let project = self.project().read(cx);
2114        let mut title = String::new();
2115
2116        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2117            let filename = path
2118                .path
2119                .file_name()
2120                .map(|s| s.to_string_lossy())
2121                .or_else(|| {
2122                    Some(Cow::Borrowed(
2123                        project
2124                            .worktree_for_id(path.worktree_id, cx)?
2125                            .read(cx)
2126                            .root_name(),
2127                    ))
2128                });
2129
2130            if let Some(filename) = filename {
2131                title.push_str(filename.as_ref());
2132                title.push_str("");
2133            }
2134        }
2135
2136        for (i, name) in project.worktree_root_names(cx).enumerate() {
2137            if i > 0 {
2138                title.push_str(", ");
2139            }
2140            title.push_str(name);
2141        }
2142
2143        if title.is_empty() {
2144            title = "empty project".to_string();
2145        }
2146
2147        if project.is_remote() {
2148            title.push_str("");
2149        } else if project.is_shared() {
2150            title.push_str("");
2151        }
2152
2153        cx.set_window_title(&title);
2154    }
2155
2156    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2157        let is_edited = !self.project.read(cx).is_read_only()
2158            && self
2159                .items(cx)
2160                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2161        if is_edited != self.window_edited {
2162            self.window_edited = is_edited;
2163            cx.set_window_edited(self.window_edited)
2164        }
2165    }
2166
2167    fn render_disconnected_overlay(
2168        &self,
2169        cx: &mut ViewContext<Workspace>,
2170    ) -> Option<AnyElement<Workspace>> {
2171        if self.project.read(cx).is_read_only() {
2172            enum DisconnectedOverlay {}
2173            Some(
2174                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
2175                    let theme = &cx.global::<Settings>().theme;
2176                    Label::new(
2177                        "Your connection to the remote project has been lost.",
2178                        theme.workspace.disconnected_overlay.text.clone(),
2179                    )
2180                    .aligned()
2181                    .contained()
2182                    .with_style(theme.workspace.disconnected_overlay.container)
2183                })
2184                .with_cursor_style(CursorStyle::Arrow)
2185                .capture_all()
2186                .into_any_named("disconnected overlay"),
2187            )
2188        } else {
2189            None
2190        }
2191    }
2192
2193    fn render_notifications(
2194        &self,
2195        theme: &theme::Workspace,
2196        cx: &AppContext,
2197    ) -> Option<AnyElement<Workspace>> {
2198        if self.notifications.is_empty() {
2199            None
2200        } else {
2201            Some(
2202                Flex::column()
2203                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2204                        ChildView::new(notification.as_any(), cx)
2205                            .contained()
2206                            .with_style(theme.notification)
2207                    }))
2208                    .constrained()
2209                    .with_width(theme.notifications.width)
2210                    .contained()
2211                    .with_style(theme.notifications.container)
2212                    .aligned()
2213                    .bottom()
2214                    .right()
2215                    .into_any(),
2216            )
2217        }
2218    }
2219
2220    // RPC handlers
2221
2222    async fn handle_follow(
2223        this: WeakViewHandle<Self>,
2224        envelope: TypedEnvelope<proto::Follow>,
2225        _: Arc<Client>,
2226        mut cx: AsyncAppContext,
2227    ) -> Result<proto::FollowResponse> {
2228        this.update(&mut cx, |this, cx| {
2229            let client = &this.client;
2230            this.leader_state
2231                .followers
2232                .insert(envelope.original_sender_id()?);
2233
2234            let active_view_id = this.active_item(cx).and_then(|i| {
2235                Some(
2236                    i.to_followable_item_handle(cx)?
2237                        .remote_id(client, cx)?
2238                        .to_proto(),
2239                )
2240            });
2241
2242            cx.notify();
2243
2244            Ok(proto::FollowResponse {
2245                active_view_id,
2246                views: this
2247                    .panes()
2248                    .iter()
2249                    .flat_map(|pane| {
2250                        let leader_id = this.leader_for_pane(pane);
2251                        pane.read(cx).items().filter_map({
2252                            let cx = &cx;
2253                            move |item| {
2254                                let item = item.to_followable_item_handle(cx)?;
2255                                let id = item.remote_id(client, cx)?.to_proto();
2256                                let variant = item.to_state_proto(cx)?;
2257                                Some(proto::View {
2258                                    id: Some(id),
2259                                    leader_id,
2260                                    variant: Some(variant),
2261                                })
2262                            }
2263                        })
2264                    })
2265                    .collect(),
2266            })
2267        })?
2268    }
2269
2270    async fn handle_unfollow(
2271        this: WeakViewHandle<Self>,
2272        envelope: TypedEnvelope<proto::Unfollow>,
2273        _: Arc<Client>,
2274        mut cx: AsyncAppContext,
2275    ) -> Result<()> {
2276        this.update(&mut cx, |this, cx| {
2277            this.leader_state
2278                .followers
2279                .remove(&envelope.original_sender_id()?);
2280            cx.notify();
2281            Ok(())
2282        })?
2283    }
2284
2285    async fn handle_update_followers(
2286        this: WeakViewHandle<Self>,
2287        envelope: TypedEnvelope<proto::UpdateFollowers>,
2288        _: Arc<Client>,
2289        cx: AsyncAppContext,
2290    ) -> Result<()> {
2291        let leader_id = envelope.original_sender_id()?;
2292        this.read_with(&cx, |this, _| {
2293            this.leader_updates_tx
2294                .unbounded_send((leader_id, envelope.payload))
2295        })??;
2296        Ok(())
2297    }
2298
2299    async fn process_leader_update(
2300        this: &WeakViewHandle<Self>,
2301        leader_id: PeerId,
2302        update: proto::UpdateFollowers,
2303        cx: &mut AsyncAppContext,
2304    ) -> Result<()> {
2305        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2306            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2307                this.update(cx, |this, _| {
2308                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2309                        for state in state.values_mut() {
2310                            state.active_view_id =
2311                                if let Some(active_view_id) = update_active_view.id.clone() {
2312                                    Some(ViewId::from_proto(active_view_id)?)
2313                                } else {
2314                                    None
2315                                };
2316                        }
2317                    }
2318                    anyhow::Ok(())
2319                })??;
2320            }
2321            proto::update_followers::Variant::UpdateView(update_view) => {
2322                let variant = update_view
2323                    .variant
2324                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2325                let id = update_view
2326                    .id
2327                    .ok_or_else(|| anyhow!("missing update view id"))?;
2328                let mut tasks = Vec::new();
2329                this.update(cx, |this, cx| {
2330                    let project = this.project.clone();
2331                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2332                        for state in state.values_mut() {
2333                            let view_id = ViewId::from_proto(id.clone())?;
2334                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2335                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2336                            }
2337                        }
2338                    }
2339                    anyhow::Ok(())
2340                })??;
2341                try_join_all(tasks).await.log_err();
2342            }
2343            proto::update_followers::Variant::CreateView(view) => {
2344                let panes = this.read_with(cx, |this, _| {
2345                    this.follower_states_by_leader
2346                        .get(&leader_id)
2347                        .into_iter()
2348                        .flat_map(|states_by_pane| states_by_pane.keys())
2349                        .cloned()
2350                        .collect()
2351                })?;
2352                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2353            }
2354        }
2355        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2356        Ok(())
2357    }
2358
2359    async fn add_views_from_leader(
2360        this: WeakViewHandle<Self>,
2361        leader_id: PeerId,
2362        panes: Vec<ViewHandle<Pane>>,
2363        views: Vec<proto::View>,
2364        cx: &mut AsyncAppContext,
2365    ) -> Result<()> {
2366        let project = this.read_with(cx, |this, _| this.project.clone())?;
2367        let replica_id = project
2368            .read_with(cx, |project, _| {
2369                project
2370                    .collaborators()
2371                    .get(&leader_id)
2372                    .map(|c| c.replica_id)
2373            })
2374            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2375
2376        let item_builders = cx.update(|cx| {
2377            cx.default_global::<FollowableItemBuilders>()
2378                .values()
2379                .map(|b| b.0)
2380                .collect::<Vec<_>>()
2381        });
2382
2383        let mut item_tasks_by_pane = HashMap::default();
2384        for pane in panes {
2385            let mut item_tasks = Vec::new();
2386            let mut leader_view_ids = Vec::new();
2387            for view in &views {
2388                let Some(id) = &view.id else { continue };
2389                let id = ViewId::from_proto(id.clone())?;
2390                let mut variant = view.variant.clone();
2391                if variant.is_none() {
2392                    Err(anyhow!("missing variant"))?;
2393                }
2394                for build_item in &item_builders {
2395                    let task = cx.update(|cx| {
2396                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2397                    });
2398                    if let Some(task) = task {
2399                        item_tasks.push(task);
2400                        leader_view_ids.push(id);
2401                        break;
2402                    } else {
2403                        assert!(variant.is_some());
2404                    }
2405                }
2406            }
2407
2408            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2409        }
2410
2411        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2412            let items = futures::future::try_join_all(item_tasks).await?;
2413            this.update(cx, |this, cx| {
2414                let state = this
2415                    .follower_states_by_leader
2416                    .get_mut(&leader_id)?
2417                    .get_mut(&pane)?;
2418
2419                for (id, item) in leader_view_ids.into_iter().zip(items) {
2420                    item.set_leader_replica_id(Some(replica_id), cx);
2421                    state.items_by_leader_view_id.insert(id, item);
2422                }
2423
2424                Some(())
2425            })?;
2426        }
2427        Ok(())
2428    }
2429
2430    fn update_followers(
2431        &self,
2432        update: proto::update_followers::Variant,
2433        cx: &AppContext,
2434    ) -> Option<()> {
2435        let project_id = self.project.read(cx).remote_id()?;
2436        if !self.leader_state.followers.is_empty() {
2437            self.client
2438                .send(proto::UpdateFollowers {
2439                    project_id,
2440                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2441                    variant: Some(update),
2442                })
2443                .log_err();
2444        }
2445        None
2446    }
2447
2448    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2449        self.follower_states_by_leader
2450            .iter()
2451            .find_map(|(leader_id, state)| {
2452                if state.contains_key(pane) {
2453                    Some(*leader_id)
2454                } else {
2455                    None
2456                }
2457            })
2458    }
2459
2460    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2461        cx.notify();
2462
2463        let call = self.active_call()?;
2464        let room = call.read(cx).room()?.read(cx);
2465        let participant = room.remote_participant_for_peer_id(leader_id)?;
2466        let mut items_to_activate = Vec::new();
2467        match participant.location {
2468            call::ParticipantLocation::SharedProject { project_id } => {
2469                if Some(project_id) == self.project.read(cx).remote_id() {
2470                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2471                        if let Some(item) = state
2472                            .active_view_id
2473                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2474                        {
2475                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2476                        } else {
2477                            if let Some(shared_screen) =
2478                                self.shared_screen_for_peer(leader_id, pane, cx)
2479                            {
2480                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2481                            }
2482                        }
2483                    }
2484                }
2485            }
2486            call::ParticipantLocation::UnsharedProject => {}
2487            call::ParticipantLocation::External => {
2488                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2489                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2490                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2491                    }
2492                }
2493            }
2494        }
2495
2496        for (pane, item) in items_to_activate {
2497            let active_item_was_focused = pane
2498                .read(cx)
2499                .active_item()
2500                .map(|active_item| cx.is_child_focused(active_item.as_any()))
2501                .unwrap_or_default();
2502
2503            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2504                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2505            } else {
2506                Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2507            }
2508
2509            if active_item_was_focused {
2510                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2511            }
2512        }
2513
2514        None
2515    }
2516
2517    fn shared_screen_for_peer(
2518        &self,
2519        peer_id: PeerId,
2520        pane: &ViewHandle<Pane>,
2521        cx: &mut ViewContext<Self>,
2522    ) -> Option<ViewHandle<SharedScreen>> {
2523        let call = self.active_call()?;
2524        let room = call.read(cx).room()?.read(cx);
2525        let participant = room.remote_participant_for_peer_id(peer_id)?;
2526        let track = participant.tracks.values().next()?.clone();
2527        let user = participant.user.clone();
2528
2529        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2530            if item.read(cx).peer_id == peer_id {
2531                return Some(item);
2532            }
2533        }
2534
2535        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2536    }
2537
2538    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2539        if active {
2540            cx.background()
2541                .spawn(persistence::DB.update_timestamp(self.database_id()))
2542                .detach();
2543        } else {
2544            for pane in &self.panes {
2545                pane.update(cx, |pane, cx| {
2546                    if let Some(item) = pane.active_item() {
2547                        item.workspace_deactivated(cx);
2548                    }
2549                    if matches!(
2550                        cx.global::<Settings>().autosave,
2551                        Autosave::OnWindowChange | Autosave::OnFocusChange
2552                    ) {
2553                        for item in pane.items() {
2554                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2555                                .detach_and_log_err(cx);
2556                        }
2557                    }
2558                });
2559            }
2560        }
2561    }
2562
2563    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2564        self.active_call.as_ref().map(|(call, _)| call)
2565    }
2566
2567    fn on_active_call_event(
2568        &mut self,
2569        _: ModelHandle<ActiveCall>,
2570        event: &call::room::Event,
2571        cx: &mut ViewContext<Self>,
2572    ) {
2573        match event {
2574            call::room::Event::ParticipantLocationChanged { participant_id }
2575            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2576                self.leader_updated(*participant_id, cx);
2577            }
2578            _ => {}
2579        }
2580    }
2581
2582    pub fn database_id(&self) -> WorkspaceId {
2583        self.database_id
2584    }
2585
2586    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2587        let project = self.project().read(cx);
2588
2589        if project.is_local() {
2590            Some(
2591                project
2592                    .visible_worktrees(cx)
2593                    .map(|worktree| worktree.read(cx).abs_path())
2594                    .collect::<Vec<_>>()
2595                    .into(),
2596            )
2597        } else {
2598            None
2599        }
2600    }
2601
2602    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2603        match member {
2604            Member::Axis(PaneAxis { members, .. }) => {
2605                for child in members.iter() {
2606                    self.remove_panes(child.clone(), cx)
2607                }
2608            }
2609            Member::Pane(pane) => self.remove_pane(pane.clone(), cx),
2610        }
2611    }
2612
2613    fn serialize_workspace(&self, cx: &AppContext) {
2614        fn serialize_pane_handle(
2615            pane_handle: &ViewHandle<Pane>,
2616            cx: &AppContext,
2617        ) -> SerializedPane {
2618            let (items, active) = {
2619                let pane = pane_handle.read(cx);
2620                let active_item_id = pane.active_item().map(|item| item.id());
2621                (
2622                    pane.items()
2623                        .filter_map(|item_handle| {
2624                            Some(SerializedItem {
2625                                kind: Arc::from(item_handle.serialized_item_kind()?),
2626                                item_id: item_handle.id(),
2627                                active: Some(item_handle.id()) == active_item_id,
2628                            })
2629                        })
2630                        .collect::<Vec<_>>(),
2631                    pane.is_active(),
2632                )
2633            };
2634
2635            SerializedPane::new(items, active)
2636        }
2637
2638        fn build_serialized_pane_group(
2639            pane_group: &Member,
2640            cx: &AppContext,
2641        ) -> SerializedPaneGroup {
2642            match pane_group {
2643                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2644                    axis: *axis,
2645                    children: members
2646                        .iter()
2647                        .map(|member| build_serialized_pane_group(member, cx))
2648                        .collect::<Vec<_>>(),
2649                },
2650                Member::Pane(pane_handle) => {
2651                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2652                }
2653            }
2654        }
2655
2656        if let Some(location) = self.location(cx) {
2657            // Load bearing special case:
2658            //  - with_local_workspace() relies on this to not have other stuff open
2659            //    when you open your log
2660            if !location.paths().is_empty() {
2661                let dock_pane = serialize_pane_handle(self.dock.pane(), cx);
2662                let center_group = build_serialized_pane_group(&self.center.root, cx);
2663
2664                let serialized_workspace = SerializedWorkspace {
2665                    id: self.database_id,
2666                    location,
2667                    dock_position: self.dock.position(),
2668                    dock_pane,
2669                    center_group,
2670                    left_sidebar_open: self.left_sidebar.read(cx).is_open(),
2671                    bounds: Default::default(),
2672                    display: Default::default(),
2673                };
2674
2675                cx.background()
2676                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2677                    .detach();
2678            }
2679        }
2680    }
2681
2682    fn load_from_serialized_workspace(
2683        workspace: WeakViewHandle<Workspace>,
2684        serialized_workspace: SerializedWorkspace,
2685        cx: &mut AppContext,
2686    ) {
2687        cx.spawn(|mut cx| async move {
2688            let (project, dock_pane_handle, old_center_pane) =
2689                workspace.read_with(&cx, |workspace, _| {
2690                    (
2691                        workspace.project().clone(),
2692                        workspace.dock_pane().downgrade(),
2693                        workspace.last_active_center_pane.clone(),
2694                    )
2695                })?;
2696
2697            serialized_workspace
2698                .dock_pane
2699                .deserialize_to(
2700                    &project,
2701                    &dock_pane_handle,
2702                    serialized_workspace.id,
2703                    &workspace,
2704                    &mut cx,
2705                )
2706                .await?;
2707
2708            // Traverse the splits tree and add to things
2709            let center_group = serialized_workspace
2710                .center_group
2711                .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2712                .await;
2713
2714            // Remove old panes from workspace panes list
2715            workspace.update(&mut cx, |workspace, cx| {
2716                if let Some((center_group, active_pane)) = center_group {
2717                    workspace.remove_panes(workspace.center.root.clone(), cx);
2718
2719                    // Swap workspace center group
2720                    workspace.center = PaneGroup::with_root(center_group);
2721
2722                    // Change the focus to the workspace first so that we retrigger focus in on the pane.
2723                    cx.focus_self();
2724
2725                    if let Some(active_pane) = active_pane {
2726                        cx.focus(&active_pane);
2727                    } else {
2728                        cx.focus(workspace.panes.last().unwrap());
2729                    }
2730                } else {
2731                    let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
2732                    if let Some(old_center_handle) = old_center_handle {
2733                        cx.focus(&old_center_handle)
2734                    } else {
2735                        cx.focus_self()
2736                    }
2737                }
2738
2739                if workspace.left_sidebar().read(cx).is_open()
2740                    != serialized_workspace.left_sidebar_open
2741                {
2742                    workspace.toggle_sidebar(SidebarSide::Left, cx);
2743                }
2744
2745                // Note that without after_window, the focus_self() and
2746                // the focus the dock generates start generating alternating
2747                // focus due to the deferred execution each triggering each other
2748                cx.after_window_update(move |workspace, cx| {
2749                    Dock::set_dock_position(
2750                        workspace,
2751                        serialized_workspace.dock_position,
2752                        true,
2753                        cx,
2754                    );
2755                });
2756
2757                cx.notify();
2758            })?;
2759
2760            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
2761            workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
2762            anyhow::Ok(())
2763        })
2764        .detach_and_log_err(cx);
2765    }
2766
2767    #[cfg(any(test, feature = "test-support"))]
2768    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
2769        Self::new(None, 0, project, |_, _| None, || &[], cx)
2770    }
2771}
2772
2773fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
2774    workspace.update(cx, |workspace, cx| {
2775        if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
2776            workspace.show_notification_once(0, cx, |cx| {
2777                cx.add_view(|_| {
2778                    MessageNotification::new(
2779                        "Failed to load any database file.",
2780                        OsOpen::new("https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml".to_string()),
2781                        "Click to let us know about this error"
2782                    )
2783                })
2784            });
2785        } else {
2786            let backup_path = (*db::BACKUP_DB_PATH).read();
2787            if let Some(backup_path) = &*backup_path {
2788                workspace.show_notification_once(0, cx, |cx| {
2789                    cx.add_view(|_| {
2790                        let backup_path = backup_path.to_string_lossy();
2791                        MessageNotification::new(
2792                            format!(
2793                                "Database file was corrupted. Old database backed up to {}",
2794                                backup_path
2795                            ),
2796                            OsOpen::new(backup_path.to_string()),
2797                            "Click to show old database in finder",
2798                        )
2799                    })
2800                });
2801            }
2802        }
2803    }).log_err();
2804}
2805
2806impl Entity for Workspace {
2807    type Event = Event;
2808}
2809
2810impl View for Workspace {
2811    fn ui_name() -> &'static str {
2812        "Workspace"
2813    }
2814
2815    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2816        let theme = cx.global::<Settings>().theme.clone();
2817        Stack::new()
2818            .with_child(
2819                Flex::column()
2820                    .with_child(self.render_titlebar(&theme, cx))
2821                    .with_child(
2822                        Stack::new()
2823                            .with_child({
2824                                let project = self.project.clone();
2825                                Flex::row()
2826                                    .with_children(
2827                                        if self.left_sidebar.read(cx).active_item().is_some() {
2828                                            Some(
2829                                                ChildView::new(&self.left_sidebar, cx)
2830                                                    .constrained()
2831                                                    .dynamically(|constraint, _, cx| {
2832                                                        SizeConstraint::new(
2833                                                            Vector2F::new(20., constraint.min.y()),
2834                                                            Vector2F::new(
2835                                                                cx.window_size().x() * 0.8,
2836                                                                constraint.max.y(),
2837                                                            ),
2838                                                        )
2839                                                    }),
2840                                            )
2841                                        } else {
2842                                            None
2843                                        },
2844                                    )
2845                                    .with_child(
2846                                        FlexItem::new(
2847                                            Flex::column()
2848                                                .with_child(
2849                                                    FlexItem::new(self.center.render(
2850                                                        &project,
2851                                                        &theme,
2852                                                        &self.follower_states_by_leader,
2853                                                        self.active_call(),
2854                                                        self.active_pane(),
2855                                                        cx,
2856                                                    ))
2857                                                    .flex(1., true),
2858                                                )
2859                                                .with_children(self.dock.render(
2860                                                    &theme,
2861                                                    DockAnchor::Bottom,
2862                                                    cx,
2863                                                )),
2864                                        )
2865                                        .flex(1., true),
2866                                    )
2867                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2868                                    .with_children(
2869                                        if self.right_sidebar.read(cx).active_item().is_some() {
2870                                            Some(
2871                                                ChildView::new(&self.right_sidebar, cx)
2872                                                    .constrained()
2873                                                    .dynamically(|constraint, _, cx| {
2874                                                        SizeConstraint::new(
2875                                                            Vector2F::new(20., constraint.min.y()),
2876                                                            Vector2F::new(
2877                                                                cx.window_size().x() * 0.8,
2878                                                                constraint.max.y(),
2879                                                            ),
2880                                                        )
2881                                                    }),
2882                                            )
2883                                        } else {
2884                                            None
2885                                        },
2886                                    )
2887                            })
2888                            .with_child(Overlay::new(
2889                                Stack::new()
2890                                    .with_children(self.dock.render(
2891                                        &theme,
2892                                        DockAnchor::Expanded,
2893                                        cx,
2894                                    ))
2895                                    .with_children(self.modal.as_ref().map(|modal| {
2896                                        ChildView::new(modal, cx)
2897                                            .contained()
2898                                            .with_style(theme.workspace.modal)
2899                                            .aligned()
2900                                            .top()
2901                                    }))
2902                                    .with_children(self.render_notifications(&theme.workspace, cx)),
2903                            ))
2904                            .flex(1.0, true),
2905                    )
2906                    .with_child(ChildView::new(&self.status_bar, cx))
2907                    .contained()
2908                    .with_background_color(theme.workspace.background),
2909            )
2910            .with_children(DragAndDrop::render(cx))
2911            .with_children(self.render_disconnected_overlay(cx))
2912            .into_any_named("workspace")
2913    }
2914
2915    fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
2916        if cx.is_self_focused() {
2917            cx.focus(&self.active_pane);
2918        } else {
2919            for pane in self.panes() {
2920                let view = view.clone();
2921                if pane.update(cx, |_, cx| view.id() == cx.view_id() || cx.is_child(view)) {
2922                    self.handle_pane_focused(pane.clone(), cx);
2923                    break;
2924                }
2925            }
2926        }
2927    }
2928
2929    fn keymap_context(&self, _: &AppContext) -> KeymapContext {
2930        Self::default_keymap_context()
2931    }
2932}
2933
2934impl ViewId {
2935    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
2936        Ok(Self {
2937            creator: message
2938                .creator
2939                .ok_or_else(|| anyhow!("creator is missing"))?,
2940            id: message.id,
2941        })
2942    }
2943
2944    pub(crate) fn to_proto(&self) -> proto::ViewId {
2945        proto::ViewId {
2946            creator: Some(self.creator),
2947            id: self.id,
2948        }
2949    }
2950}
2951
2952pub trait WorkspaceHandle {
2953    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2954}
2955
2956impl WorkspaceHandle for ViewHandle<Workspace> {
2957    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2958        self.read(cx)
2959            .worktrees(cx)
2960            .flat_map(|worktree| {
2961                let worktree_id = worktree.read(cx).id();
2962                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2963                    worktree_id,
2964                    path: f.path.clone(),
2965                })
2966            })
2967            .collect::<Vec<_>>()
2968    }
2969}
2970
2971impl std::fmt::Debug for OpenPaths {
2972    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2973        f.debug_struct("OpenPaths")
2974            .field("paths", &self.paths)
2975            .finish()
2976    }
2977}
2978
2979pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2980
2981pub fn activate_workspace_for_project(
2982    cx: &mut AppContext,
2983    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2984) -> Option<WeakViewHandle<Workspace>> {
2985    for window_id in cx.window_ids().collect::<Vec<_>>() {
2986        let handle = cx
2987            .update_window(window_id, |cx| {
2988                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
2989                    let project = workspace_handle.read(cx).project.clone();
2990                    if project.update(cx, &predicate) {
2991                        cx.activate_window();
2992                        return Some(workspace_handle.clone());
2993                    }
2994                }
2995                None
2996            })
2997            .flatten();
2998
2999        if let Some(handle) = handle {
3000            return Some(handle.downgrade());
3001        }
3002    }
3003    None
3004}
3005
3006pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3007    DB.last_workspace().await.log_err().flatten()
3008}
3009
3010#[allow(clippy::type_complexity)]
3011pub fn open_paths(
3012    abs_paths: &[PathBuf],
3013    app_state: &Arc<AppState>,
3014    requesting_window_id: Option<usize>,
3015    cx: &mut AppContext,
3016) -> Task<
3017    Result<(
3018        WeakViewHandle<Workspace>,
3019        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3020    )>,
3021> {
3022    log::info!("open paths {:?}", abs_paths);
3023
3024    // Open paths in existing workspace if possible
3025    let existing =
3026        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
3027
3028    let app_state = app_state.clone();
3029    let abs_paths = abs_paths.to_vec();
3030    cx.spawn(|mut cx| async move {
3031        if let Some(existing) = existing {
3032            Ok((
3033                existing.clone(),
3034                existing
3035                    .update(&mut cx, |workspace, cx| {
3036                        workspace.open_paths(abs_paths, true, cx)
3037                    })?
3038                    .await,
3039            ))
3040        } else {
3041            let contains_directory =
3042                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
3043                    .await
3044                    .contains(&false);
3045
3046            cx.update(|cx| {
3047                let task =
3048                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx);
3049
3050                cx.spawn(|mut cx| async move {
3051                    let (workspace, items) = task.await;
3052
3053                    workspace.update(&mut cx, |workspace, cx| {
3054                        if contains_directory {
3055                            workspace.toggle_sidebar(SidebarSide::Left, cx);
3056                        }
3057                    })?;
3058
3059                    anyhow::Ok((workspace, items))
3060                })
3061            })
3062            .await
3063        }
3064    })
3065}
3066
3067pub fn open_new(
3068    app_state: &Arc<AppState>,
3069    cx: &mut AppContext,
3070    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3071) -> Task<()> {
3072    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3073    cx.spawn(|mut cx| async move {
3074        let (workspace, opened_paths) = task.await;
3075
3076        workspace
3077            .update(&mut cx, |workspace, cx| {
3078                if opened_paths.is_empty() {
3079                    init(workspace, cx)
3080                }
3081            })
3082            .log_err();
3083    })
3084}
3085
3086fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3087    let mut parts = value.split(',');
3088    let width: usize = parts.next()?.parse().ok()?;
3089    let height: usize = parts.next()?.parse().ok()?;
3090    Some(vec2f(width as f32, height as f32))
3091}
3092
3093#[cfg(test)]
3094mod tests {
3095    use std::{cell::RefCell, rc::Rc};
3096
3097    use crate::item::test::{TestItem, TestItemEvent, TestProjectItem};
3098
3099    use super::*;
3100    use fs::FakeFs;
3101    use gpui::{executor::Deterministic, TestAppContext};
3102    use project::{Project, ProjectEntryId};
3103    use serde_json::json;
3104
3105    #[gpui::test]
3106    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3107        cx.foreground().forbid_parking();
3108        Settings::test_async(cx);
3109
3110        let fs = FakeFs::new(cx.background());
3111        let project = Project::test(fs, [], cx).await;
3112        let (_, workspace) = cx.add_window(|cx| {
3113            Workspace::new(
3114                Default::default(),
3115                0,
3116                project.clone(),
3117                |_, _| None,
3118                || &[],
3119                cx,
3120            )
3121        });
3122
3123        // Adding an item with no ambiguity renders the tab without detail.
3124        let item1 = cx.add_view(&workspace, |_| {
3125            let mut item = TestItem::new();
3126            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3127            item
3128        });
3129        workspace.update(cx, |workspace, cx| {
3130            workspace.add_item(Box::new(item1.clone()), cx);
3131        });
3132        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3133
3134        // Adding an item that creates ambiguity increases the level of detail on
3135        // both tabs.
3136        let item2 = cx.add_view(&workspace, |_| {
3137            let mut item = TestItem::new();
3138            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3139            item
3140        });
3141        workspace.update(cx, |workspace, cx| {
3142            workspace.add_item(Box::new(item2.clone()), cx);
3143        });
3144        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3145        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3146
3147        // Adding an item that creates ambiguity increases the level of detail only
3148        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3149        // we stop at the highest detail available.
3150        let item3 = cx.add_view(&workspace, |_| {
3151            let mut item = TestItem::new();
3152            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3153            item
3154        });
3155        workspace.update(cx, |workspace, cx| {
3156            workspace.add_item(Box::new(item3.clone()), cx);
3157        });
3158        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3159        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3160        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3161    }
3162
3163    #[gpui::test]
3164    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3165        cx.foreground().forbid_parking();
3166        Settings::test_async(cx);
3167        let fs = FakeFs::new(cx.background());
3168        fs.insert_tree(
3169            "/root1",
3170            json!({
3171                "one.txt": "",
3172                "two.txt": "",
3173            }),
3174        )
3175        .await;
3176        fs.insert_tree(
3177            "/root2",
3178            json!({
3179                "three.txt": "",
3180            }),
3181        )
3182        .await;
3183
3184        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3185        let (window_id, workspace) = cx.add_window(|cx| {
3186            Workspace::new(
3187                Default::default(),
3188                0,
3189                project.clone(),
3190                |_, _| None,
3191                || &[],
3192                cx,
3193            )
3194        });
3195        let worktree_id = project.read_with(cx, |project, cx| {
3196            project.worktrees(cx).next().unwrap().read(cx).id()
3197        });
3198
3199        let item1 = cx.add_view(&workspace, |cx| {
3200            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3201        });
3202        let item2 = cx.add_view(&workspace, |cx| {
3203            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3204        });
3205
3206        // Add an item to an empty pane
3207        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3208        project.read_with(cx, |project, cx| {
3209            assert_eq!(
3210                project.active_entry(),
3211                project
3212                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3213                    .map(|e| e.id)
3214            );
3215        });
3216        assert_eq!(
3217            cx.current_window_title(window_id).as_deref(),
3218            Some("one.txt — root1")
3219        );
3220
3221        // Add a second item to a non-empty pane
3222        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3223        assert_eq!(
3224            cx.current_window_title(window_id).as_deref(),
3225            Some("two.txt — root1")
3226        );
3227        project.read_with(cx, |project, cx| {
3228            assert_eq!(
3229                project.active_entry(),
3230                project
3231                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3232                    .map(|e| e.id)
3233            );
3234        });
3235
3236        // Close the active item
3237        workspace
3238            .update(cx, |workspace, cx| {
3239                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
3240            })
3241            .await
3242            .unwrap();
3243        assert_eq!(
3244            cx.current_window_title(window_id).as_deref(),
3245            Some("one.txt — root1")
3246        );
3247        project.read_with(cx, |project, cx| {
3248            assert_eq!(
3249                project.active_entry(),
3250                project
3251                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3252                    .map(|e| e.id)
3253            );
3254        });
3255
3256        // Add a project folder
3257        project
3258            .update(cx, |project, cx| {
3259                project.find_or_create_local_worktree("/root2", true, cx)
3260            })
3261            .await
3262            .unwrap();
3263        assert_eq!(
3264            cx.current_window_title(window_id).as_deref(),
3265            Some("one.txt — root1, root2")
3266        );
3267
3268        // Remove a project folder
3269        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3270        assert_eq!(
3271            cx.current_window_title(window_id).as_deref(),
3272            Some("one.txt — root2")
3273        );
3274    }
3275
3276    #[gpui::test]
3277    async fn test_close_window(cx: &mut TestAppContext) {
3278        cx.foreground().forbid_parking();
3279        Settings::test_async(cx);
3280        let fs = FakeFs::new(cx.background());
3281        fs.insert_tree("/root", json!({ "one": "" })).await;
3282
3283        let project = Project::test(fs, ["root".as_ref()], cx).await;
3284        let (window_id, workspace) = cx.add_window(|cx| {
3285            Workspace::new(
3286                Default::default(),
3287                0,
3288                project.clone(),
3289                |_, _| None,
3290                || &[],
3291                cx,
3292            )
3293        });
3294
3295        // When there are no dirty items, there's nothing to do.
3296        let item1 = cx.add_view(&workspace, |_| TestItem::new());
3297        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3298        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3299        assert!(task.await.unwrap());
3300
3301        // When there are dirty untitled items, prompt to save each one. If the user
3302        // cancels any prompt, then abort.
3303        let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true));
3304        let item3 = cx.add_view(&workspace, |cx| {
3305            TestItem::new()
3306                .with_dirty(true)
3307                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3308        });
3309        workspace.update(cx, |w, cx| {
3310            w.add_item(Box::new(item2.clone()), cx);
3311            w.add_item(Box::new(item3.clone()), cx);
3312        });
3313        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3314        cx.foreground().run_until_parked();
3315        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3316        cx.foreground().run_until_parked();
3317        assert!(!cx.has_pending_prompt(window_id));
3318        assert!(!task.await.unwrap());
3319    }
3320
3321    #[gpui::test]
3322    async fn test_close_pane_items(cx: &mut TestAppContext) {
3323        cx.foreground().forbid_parking();
3324        Settings::test_async(cx);
3325        let fs = FakeFs::new(cx.background());
3326
3327        let project = Project::test(fs, None, cx).await;
3328        let (window_id, workspace) = cx.add_window(|cx| {
3329            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3330        });
3331
3332        let item1 = cx.add_view(&workspace, |cx| {
3333            TestItem::new()
3334                .with_dirty(true)
3335                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3336        });
3337        let item2 = cx.add_view(&workspace, |cx| {
3338            TestItem::new()
3339                .with_dirty(true)
3340                .with_conflict(true)
3341                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3342        });
3343        let item3 = cx.add_view(&workspace, |cx| {
3344            TestItem::new()
3345                .with_dirty(true)
3346                .with_conflict(true)
3347                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3348        });
3349        let item4 = cx.add_view(&workspace, |cx| {
3350            TestItem::new()
3351                .with_dirty(true)
3352                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3353        });
3354        let pane = workspace.update(cx, |workspace, cx| {
3355            workspace.add_item(Box::new(item1.clone()), cx);
3356            workspace.add_item(Box::new(item2.clone()), cx);
3357            workspace.add_item(Box::new(item3.clone()), cx);
3358            workspace.add_item(Box::new(item4.clone()), cx);
3359            workspace.active_pane().clone()
3360        });
3361
3362        let close_items = workspace.update(cx, |workspace, cx| {
3363            pane.update(cx, |pane, cx| {
3364                pane.activate_item(1, true, true, cx);
3365                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3366            });
3367
3368            let item1_id = item1.id();
3369            let item3_id = item3.id();
3370            let item4_id = item4.id();
3371            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3372                [item1_id, item3_id, item4_id].contains(&id)
3373            })
3374        });
3375        cx.foreground().run_until_parked();
3376
3377        // There's a prompt to save item 1.
3378        pane.read_with(cx, |pane, _| {
3379            assert_eq!(pane.items_len(), 4);
3380            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3381        });
3382        assert!(cx.has_pending_prompt(window_id));
3383
3384        // Confirm saving item 1.
3385        cx.simulate_prompt_answer(window_id, 0);
3386        cx.foreground().run_until_parked();
3387
3388        // Item 1 is saved. There's a prompt to save item 3.
3389        pane.read_with(cx, |pane, cx| {
3390            assert_eq!(item1.read(cx).save_count, 1);
3391            assert_eq!(item1.read(cx).save_as_count, 0);
3392            assert_eq!(item1.read(cx).reload_count, 0);
3393            assert_eq!(pane.items_len(), 3);
3394            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3395        });
3396        assert!(cx.has_pending_prompt(window_id));
3397
3398        // Cancel saving item 3.
3399        cx.simulate_prompt_answer(window_id, 1);
3400        cx.foreground().run_until_parked();
3401
3402        // Item 3 is reloaded. There's a prompt to save item 4.
3403        pane.read_with(cx, |pane, cx| {
3404            assert_eq!(item3.read(cx).save_count, 0);
3405            assert_eq!(item3.read(cx).save_as_count, 0);
3406            assert_eq!(item3.read(cx).reload_count, 1);
3407            assert_eq!(pane.items_len(), 2);
3408            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3409        });
3410        assert!(cx.has_pending_prompt(window_id));
3411
3412        // Confirm saving item 4.
3413        cx.simulate_prompt_answer(window_id, 0);
3414        cx.foreground().run_until_parked();
3415
3416        // There's a prompt for a path for item 4.
3417        cx.simulate_new_path_selection(|_| Some(Default::default()));
3418        close_items.await.unwrap();
3419
3420        // The requested items are closed.
3421        pane.read_with(cx, |pane, cx| {
3422            assert_eq!(item4.read(cx).save_count, 0);
3423            assert_eq!(item4.read(cx).save_as_count, 1);
3424            assert_eq!(item4.read(cx).reload_count, 0);
3425            assert_eq!(pane.items_len(), 1);
3426            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3427        });
3428    }
3429
3430    #[gpui::test]
3431    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3432        cx.foreground().forbid_parking();
3433        Settings::test_async(cx);
3434        let fs = FakeFs::new(cx.background());
3435
3436        let project = Project::test(fs, [], cx).await;
3437        let (window_id, workspace) = cx.add_window(|cx| {
3438            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3439        });
3440
3441        // Create several workspace items with single project entries, and two
3442        // workspace items with multiple project entries.
3443        let single_entry_items = (0..=4)
3444            .map(|project_entry_id| {
3445                cx.add_view(&workspace, |cx| {
3446                    TestItem::new()
3447                        .with_dirty(true)
3448                        .with_project_items(&[TestProjectItem::new(
3449                            project_entry_id,
3450                            &format!("{project_entry_id}.txt"),
3451                            cx,
3452                        )])
3453                })
3454            })
3455            .collect::<Vec<_>>();
3456        let item_2_3 = cx.add_view(&workspace, |cx| {
3457            TestItem::new()
3458                .with_dirty(true)
3459                .with_singleton(false)
3460                .with_project_items(&[
3461                    single_entry_items[2].read(cx).project_items[0].clone(),
3462                    single_entry_items[3].read(cx).project_items[0].clone(),
3463                ])
3464        });
3465        let item_3_4 = cx.add_view(&workspace, |cx| {
3466            TestItem::new()
3467                .with_dirty(true)
3468                .with_singleton(false)
3469                .with_project_items(&[
3470                    single_entry_items[3].read(cx).project_items[0].clone(),
3471                    single_entry_items[4].read(cx).project_items[0].clone(),
3472                ])
3473        });
3474
3475        // Create two panes that contain the following project entries:
3476        //   left pane:
3477        //     multi-entry items:   (2, 3)
3478        //     single-entry items:  0, 1, 2, 3, 4
3479        //   right pane:
3480        //     single-entry items:  1
3481        //     multi-entry items:   (3, 4)
3482        let left_pane = workspace.update(cx, |workspace, cx| {
3483            let left_pane = workspace.active_pane().clone();
3484            workspace.add_item(Box::new(item_2_3.clone()), cx);
3485            for item in single_entry_items {
3486                workspace.add_item(Box::new(item), cx);
3487            }
3488            left_pane.update(cx, |pane, cx| {
3489                pane.activate_item(2, true, true, cx);
3490            });
3491
3492            workspace
3493                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3494                .unwrap();
3495
3496            left_pane
3497        });
3498
3499        //Need to cause an effect flush in order to respect new focus
3500        workspace.update(cx, |workspace, cx| {
3501            workspace.add_item(Box::new(item_3_4.clone()), cx);
3502            cx.focus(&left_pane);
3503        });
3504
3505        // When closing all of the items in the left pane, we should be prompted twice:
3506        // once for project entry 0, and once for project entry 2. After those two
3507        // prompts, the task should complete.
3508
3509        let close = workspace.update(cx, |workspace, cx| {
3510            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3511        });
3512
3513        cx.foreground().run_until_parked();
3514        left_pane.read_with(cx, |pane, cx| {
3515            assert_eq!(
3516                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3517                &[ProjectEntryId::from_proto(0)]
3518            );
3519        });
3520        cx.simulate_prompt_answer(window_id, 0);
3521
3522        cx.foreground().run_until_parked();
3523        left_pane.read_with(cx, |pane, cx| {
3524            assert_eq!(
3525                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3526                &[ProjectEntryId::from_proto(2)]
3527            );
3528        });
3529        cx.simulate_prompt_answer(window_id, 0);
3530
3531        cx.foreground().run_until_parked();
3532        close.await.unwrap();
3533        left_pane.read_with(cx, |pane, _| {
3534            assert_eq!(pane.items_len(), 0);
3535        });
3536    }
3537
3538    #[gpui::test]
3539    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3540        deterministic.forbid_parking();
3541
3542        Settings::test_async(cx);
3543        let fs = FakeFs::new(cx.background());
3544
3545        let project = Project::test(fs, [], cx).await;
3546        let (window_id, workspace) = cx.add_window(|cx| {
3547            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3548        });
3549
3550        let item = cx.add_view(&workspace, |cx| {
3551            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3552        });
3553        let item_id = item.id();
3554        workspace.update(cx, |workspace, cx| {
3555            workspace.add_item(Box::new(item.clone()), cx);
3556        });
3557
3558        // Autosave on window change.
3559        item.update(cx, |item, cx| {
3560            cx.update_global(|settings: &mut Settings, _| {
3561                settings.autosave = Autosave::OnWindowChange;
3562            });
3563            item.is_dirty = true;
3564        });
3565
3566        // Deactivating the window saves the file.
3567        cx.simulate_window_activation(None);
3568        deterministic.run_until_parked();
3569        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3570
3571        // Autosave on focus change.
3572        item.update(cx, |item, cx| {
3573            cx.focus_self();
3574            cx.update_global(|settings: &mut Settings, _| {
3575                settings.autosave = Autosave::OnFocusChange;
3576            });
3577            item.is_dirty = true;
3578        });
3579
3580        // Blurring the item saves the file.
3581        item.update(cx, |_, cx| cx.blur());
3582        deterministic.run_until_parked();
3583        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3584
3585        // Deactivating the window still saves the file.
3586        cx.simulate_window_activation(Some(window_id));
3587        item.update(cx, |item, cx| {
3588            cx.focus_self();
3589            item.is_dirty = true;
3590        });
3591        cx.simulate_window_activation(None);
3592
3593        deterministic.run_until_parked();
3594        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3595
3596        // Autosave after delay.
3597        item.update(cx, |item, cx| {
3598            cx.update_global(|settings: &mut Settings, _| {
3599                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3600            });
3601            item.is_dirty = true;
3602            cx.emit(TestItemEvent::Edit);
3603        });
3604
3605        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3606        deterministic.advance_clock(Duration::from_millis(250));
3607        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3608
3609        // After delay expires, the file is saved.
3610        deterministic.advance_clock(Duration::from_millis(250));
3611        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3612
3613        // Autosave on focus change, ensuring closing the tab counts as such.
3614        item.update(cx, |item, cx| {
3615            cx.update_global(|settings: &mut Settings, _| {
3616                settings.autosave = Autosave::OnFocusChange;
3617            });
3618            item.is_dirty = true;
3619        });
3620
3621        workspace
3622            .update(cx, |workspace, cx| {
3623                let pane = workspace.active_pane().clone();
3624                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3625            })
3626            .await
3627            .unwrap();
3628        assert!(!cx.has_pending_prompt(window_id));
3629        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3630
3631        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3632        workspace.update(cx, |workspace, cx| {
3633            workspace.add_item(Box::new(item.clone()), cx);
3634        });
3635        item.update(cx, |item, cx| {
3636            item.project_items[0].update(cx, |item, _| {
3637                item.entry_id = None;
3638            });
3639            item.is_dirty = true;
3640            cx.blur();
3641        });
3642        deterministic.run_until_parked();
3643        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3644
3645        // Ensure autosave is prevented for deleted files also when closing the buffer.
3646        let _close_items = workspace.update(cx, |workspace, cx| {
3647            let pane = workspace.active_pane().clone();
3648            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3649        });
3650        deterministic.run_until_parked();
3651        assert!(cx.has_pending_prompt(window_id));
3652        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3653    }
3654
3655    #[gpui::test]
3656    async fn test_pane_navigation(
3657        deterministic: Arc<Deterministic>,
3658        cx: &mut gpui::TestAppContext,
3659    ) {
3660        deterministic.forbid_parking();
3661        Settings::test_async(cx);
3662        let fs = FakeFs::new(cx.background());
3663
3664        let project = Project::test(fs, [], cx).await;
3665        let (_, workspace) = cx.add_window(|cx| {
3666            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3667        });
3668
3669        let item = cx.add_view(&workspace, |cx| {
3670            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3671        });
3672        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3673        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3674        let toolbar_notify_count = Rc::new(RefCell::new(0));
3675
3676        workspace.update(cx, |workspace, cx| {
3677            workspace.add_item(Box::new(item.clone()), cx);
3678            let toolbar_notification_count = toolbar_notify_count.clone();
3679            cx.observe(&toolbar, move |_, _, _| {
3680                *toolbar_notification_count.borrow_mut() += 1
3681            })
3682            .detach();
3683        });
3684
3685        pane.read_with(cx, |pane, _| {
3686            assert!(!pane.can_navigate_backward());
3687            assert!(!pane.can_navigate_forward());
3688        });
3689
3690        item.update(cx, |item, cx| {
3691            item.set_state("one".to_string(), cx);
3692        });
3693
3694        // Toolbar must be notified to re-render the navigation buttons
3695        assert_eq!(*toolbar_notify_count.borrow(), 1);
3696
3697        pane.read_with(cx, |pane, _| {
3698            assert!(pane.can_navigate_backward());
3699            assert!(!pane.can_navigate_forward());
3700        });
3701
3702        workspace
3703            .update(cx, |workspace, cx| {
3704                Pane::go_back(workspace, Some(pane.downgrade()), cx)
3705            })
3706            .await
3707            .unwrap();
3708
3709        assert_eq!(*toolbar_notify_count.borrow(), 3);
3710        pane.read_with(cx, |pane, _| {
3711            assert!(!pane.can_navigate_backward());
3712            assert!(pane.can_navigate_forward());
3713        });
3714    }
3715}