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