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