workspace.rs

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