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