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