workspace.rs

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