workspace.rs

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