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