workspace.rs

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