workspace.rs

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