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