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
1068                            .update(&mut cx, |call, cx| call.hang_up(cx))
1069                            .await
1070                            .log_err();
1071                    }
1072                }
1073            }
1074
1075            Ok(this
1076                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))
1077                .await?)
1078        })
1079    }
1080
1081    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1082        let save_all = self.save_all_internal(false, cx);
1083        Some(cx.foreground().spawn(async move {
1084            save_all.await?;
1085            Ok(())
1086        }))
1087    }
1088
1089    fn save_all_internal(
1090        &mut self,
1091        should_prompt_to_save: bool,
1092        cx: &mut ViewContext<Self>,
1093    ) -> Task<Result<bool>> {
1094        if self.project.read(cx).is_read_only() {
1095            return Task::ready(Ok(true));
1096        }
1097
1098        let dirty_items = self
1099            .panes
1100            .iter()
1101            .flat_map(|pane| {
1102                pane.read(cx).items().filter_map(|item| {
1103                    if item.is_dirty(cx) {
1104                        Some((pane.clone(), item.boxed_clone()))
1105                    } else {
1106                        None
1107                    }
1108                })
1109            })
1110            .collect::<Vec<_>>();
1111
1112        let project = self.project.clone();
1113        cx.spawn_weak(|_, mut cx| async move {
1114            for (pane, item) in dirty_items {
1115                let (singleton, project_entry_ids) =
1116                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1117                if singleton || !project_entry_ids.is_empty() {
1118                    if let Some(ix) =
1119                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))
1120                    {
1121                        if !Pane::save_item(
1122                            project.clone(),
1123                            &pane,
1124                            ix,
1125                            &*item,
1126                            should_prompt_to_save,
1127                            &mut cx,
1128                        )
1129                        .await?
1130                        {
1131                            return Ok(false);
1132                        }
1133                    }
1134                }
1135            }
1136            Ok(true)
1137        })
1138    }
1139
1140    #[allow(clippy::type_complexity)]
1141    pub fn open_paths(
1142        &mut self,
1143        mut abs_paths: Vec<PathBuf>,
1144        visible: bool,
1145        cx: &mut ViewContext<Self>,
1146    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1147        let fs = self.fs.clone();
1148
1149        // Sort the paths to ensure we add worktrees for parents before their children.
1150        abs_paths.sort_unstable();
1151        cx.spawn(|this, mut cx| async move {
1152            let mut project_paths = Vec::new();
1153            for path in &abs_paths {
1154                project_paths.push(
1155                    this.update(&mut cx, |this, cx| {
1156                        Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
1157                    })
1158                    .await
1159                    .log_err(),
1160                );
1161            }
1162
1163            let tasks = abs_paths
1164                .iter()
1165                .cloned()
1166                .zip(project_paths.into_iter())
1167                .map(|(abs_path, project_path)| {
1168                    let this = this.clone();
1169                    cx.spawn(|mut cx| {
1170                        let fs = fs.clone();
1171                        async move {
1172                            let (_worktree, project_path) = project_path?;
1173                            if fs.is_file(&abs_path).await {
1174                                Some(
1175                                    this.update(&mut cx, |this, cx| {
1176                                        this.open_path(project_path, None, true, cx)
1177                                    })
1178                                    .await,
1179                                )
1180                            } else {
1181                                None
1182                            }
1183                        }
1184                    })
1185                })
1186                .collect::<Vec<_>>();
1187
1188            futures::future::join_all(tasks).await
1189        })
1190    }
1191
1192    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1193        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1194            files: false,
1195            directories: true,
1196            multiple: true,
1197        });
1198        cx.spawn(|this, mut cx| async move {
1199            if let Some(paths) = paths.recv().await.flatten() {
1200                let results = this
1201                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
1202                    .await;
1203                for result in results.into_iter().flatten() {
1204                    result.log_err();
1205                }
1206            }
1207        })
1208        .detach();
1209    }
1210
1211    fn remove_folder_from_project(
1212        &mut self,
1213        RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
1214        cx: &mut ViewContext<Self>,
1215    ) {
1216        let future = self
1217            .project
1218            .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1219        cx.foreground().spawn(future).detach();
1220    }
1221
1222    fn project_path_for_path(
1223        project: ModelHandle<Project>,
1224        abs_path: &Path,
1225        visible: bool,
1226        cx: &mut MutableAppContext,
1227    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1228        let entry = project.update(cx, |project, cx| {
1229            project.find_or_create_local_worktree(abs_path, visible, cx)
1230        });
1231        cx.spawn(|cx| async move {
1232            let (worktree, path) = entry.await?;
1233            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1234            Ok((
1235                worktree,
1236                ProjectPath {
1237                    worktree_id,
1238                    path: path.into(),
1239                },
1240            ))
1241        })
1242    }
1243
1244    /// Returns the modal that was toggled closed if it was open.
1245    pub fn toggle_modal<V, F>(
1246        &mut self,
1247        cx: &mut ViewContext<Self>,
1248        add_view: F,
1249    ) -> Option<ViewHandle<V>>
1250    where
1251        V: 'static + View,
1252        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1253    {
1254        cx.notify();
1255        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1256        // it. Otherwise, create a new modal and set it as active.
1257        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1258        if let Some(already_open_modal) = already_open_modal {
1259            cx.focus_self();
1260            Some(already_open_modal)
1261        } else {
1262            let modal = add_view(self, cx);
1263            cx.focus(&modal);
1264            self.modal = Some(modal.into());
1265            None
1266        }
1267    }
1268
1269    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1270        self.modal
1271            .as_ref()
1272            .and_then(|modal| modal.clone().downcast::<V>())
1273    }
1274
1275    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1276        if self.modal.take().is_some() {
1277            cx.focus(&self.active_pane);
1278            cx.notify();
1279        }
1280    }
1281
1282    pub fn items<'a>(
1283        &'a self,
1284        cx: &'a AppContext,
1285    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1286        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1287    }
1288
1289    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1290        self.items_of_type(cx).max_by_key(|item| item.id())
1291    }
1292
1293    pub fn items_of_type<'a, T: Item>(
1294        &'a self,
1295        cx: &'a AppContext,
1296    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1297        self.panes
1298            .iter()
1299            .flat_map(|pane| pane.read(cx).items_of_type())
1300    }
1301
1302    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1303        self.active_pane().read(cx).active_item()
1304    }
1305
1306    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1307        self.active_item(cx).and_then(|item| item.project_path(cx))
1308    }
1309
1310    pub fn save_active_item(
1311        &mut self,
1312        force_name_change: bool,
1313        cx: &mut ViewContext<Self>,
1314    ) -> Task<Result<()>> {
1315        let project = self.project.clone();
1316        if let Some(item) = self.active_item(cx) {
1317            if !force_name_change && item.can_save(cx) {
1318                if item.has_conflict(cx.as_ref()) {
1319                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1320
1321                    let mut answer = cx.prompt(
1322                        PromptLevel::Warning,
1323                        CONFLICT_MESSAGE,
1324                        &["Overwrite", "Cancel"],
1325                    );
1326                    cx.spawn(|_, mut cx| async move {
1327                        let answer = answer.recv().await;
1328                        if answer == Some(0) {
1329                            cx.update(|cx| item.save(project, cx)).await?;
1330                        }
1331                        Ok(())
1332                    })
1333                } else {
1334                    item.save(project, cx)
1335                }
1336            } else if item.is_singleton(cx) {
1337                let worktree = self.worktrees(cx).next();
1338                let start_abs_path = worktree
1339                    .and_then(|w| w.read(cx).as_local())
1340                    .map_or(Path::new(""), |w| w.abs_path())
1341                    .to_path_buf();
1342                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1343                cx.spawn(|_, mut cx| async move {
1344                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1345                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1346                    }
1347                    Ok(())
1348                })
1349            } else {
1350                Task::ready(Ok(()))
1351            }
1352        } else {
1353            Task::ready(Ok(()))
1354        }
1355    }
1356
1357    pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext<Self>) {
1358        let sidebar = match sidebar_side {
1359            SidebarSide::Left => &mut self.left_sidebar,
1360            SidebarSide::Right => &mut self.right_sidebar,
1361        };
1362        let open = sidebar.update(cx, |sidebar, cx| {
1363            let open = !sidebar.is_open();
1364            sidebar.set_open(open, cx);
1365            open
1366        });
1367
1368        if open {
1369            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1370        }
1371
1372        self.serialize_workspace(cx);
1373
1374        cx.focus_self();
1375        cx.notify();
1376    }
1377
1378    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1379        let sidebar = match action.sidebar_side {
1380            SidebarSide::Left => &mut self.left_sidebar,
1381            SidebarSide::Right => &mut self.right_sidebar,
1382        };
1383        let active_item = sidebar.update(cx, move |sidebar, cx| {
1384            if sidebar.is_open() && sidebar.active_item_ix() == action.item_index {
1385                sidebar.set_open(false, cx);
1386                None
1387            } else {
1388                sidebar.set_open(true, cx);
1389                sidebar.activate_item(action.item_index, cx);
1390                sidebar.active_item().cloned()
1391            }
1392        });
1393
1394        if let Some(active_item) = active_item {
1395            Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx);
1396
1397            if active_item.is_focused(cx) {
1398                cx.focus_self();
1399            } else {
1400                cx.focus(active_item.to_any());
1401            }
1402        } else {
1403            cx.focus_self();
1404        }
1405
1406        self.serialize_workspace(cx);
1407
1408        cx.notify();
1409    }
1410
1411    pub fn toggle_sidebar_item_focus(
1412        &mut self,
1413        sidebar_side: SidebarSide,
1414        item_index: usize,
1415        cx: &mut ViewContext<Self>,
1416    ) {
1417        let sidebar = match sidebar_side {
1418            SidebarSide::Left => &mut self.left_sidebar,
1419            SidebarSide::Right => &mut self.right_sidebar,
1420        };
1421        let active_item = sidebar.update(cx, |sidebar, cx| {
1422            sidebar.set_open(true, cx);
1423            sidebar.activate_item(item_index, cx);
1424            sidebar.active_item().cloned()
1425        });
1426        if let Some(active_item) = active_item {
1427            Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1428
1429            if active_item.is_focused(cx) {
1430                cx.focus_self();
1431            } else {
1432                cx.focus(active_item.to_any());
1433            }
1434        }
1435
1436        self.serialize_workspace(cx);
1437
1438        cx.notify();
1439    }
1440
1441    pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1442        cx.focus_self();
1443        cx.notify();
1444    }
1445
1446    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1447        let pane =
1448            cx.add_view(|cx| Pane::new(self.weak_handle().id(), None, self.background_actions, cx));
1449        let pane_id = pane.id();
1450        cx.subscribe(&pane, move |this, _, event, cx| {
1451            this.handle_pane_event(pane_id, event, cx)
1452        })
1453        .detach();
1454        self.panes.push(pane.clone());
1455        cx.focus(pane.clone());
1456        cx.emit(Event::PaneAdded(pane.clone()));
1457        pane
1458    }
1459
1460    pub fn add_item_to_center(
1461        &mut self,
1462        item: Box<dyn ItemHandle>,
1463        cx: &mut ViewContext<Self>,
1464    ) -> bool {
1465        if let Some(center_pane) = self.last_active_center_pane.clone() {
1466            if let Some(center_pane) = center_pane.upgrade(cx) {
1467                Pane::add_item(self, &center_pane, item, true, true, None, cx);
1468                true
1469            } else {
1470                false
1471            }
1472        } else {
1473            false
1474        }
1475    }
1476
1477    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1478        let active_pane = self.active_pane().clone();
1479        Pane::add_item(self, &active_pane, item, true, true, None, cx);
1480    }
1481
1482    pub fn open_path(
1483        &mut self,
1484        path: impl Into<ProjectPath>,
1485        pane: Option<WeakViewHandle<Pane>>,
1486        focus_item: bool,
1487        cx: &mut ViewContext<Self>,
1488    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1489        let pane = pane.unwrap_or_else(|| {
1490            if !self.dock_active() {
1491                self.active_pane().downgrade()
1492            } else {
1493                self.last_active_center_pane.clone().unwrap_or_else(|| {
1494                    self.panes
1495                        .first()
1496                        .expect("There must be an active pane")
1497                        .downgrade()
1498                })
1499            }
1500        });
1501
1502        let task = self.load_path(path.into(), cx);
1503        cx.spawn(|this, mut cx| async move {
1504            let (project_entry_id, build_item) = task.await?;
1505            let pane = pane
1506                .upgrade(&cx)
1507                .ok_or_else(|| anyhow!("pane was closed"))?;
1508            this.update(&mut cx, |this, cx| {
1509                Ok(Pane::open_item(
1510                    this,
1511                    pane,
1512                    project_entry_id,
1513                    focus_item,
1514                    cx,
1515                    build_item,
1516                ))
1517            })
1518        })
1519    }
1520
1521    pub(crate) fn load_path(
1522        &mut self,
1523        path: ProjectPath,
1524        cx: &mut ViewContext<Self>,
1525    ) -> Task<
1526        Result<(
1527            ProjectEntryId,
1528            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1529        )>,
1530    > {
1531        let project = self.project().clone();
1532        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1533        cx.as_mut().spawn(|mut cx| async move {
1534            let (project_entry_id, project_item) = project_item.await?;
1535            let build_item = cx.update(|cx| {
1536                cx.default_global::<ProjectItemBuilders>()
1537                    .get(&project_item.model_type())
1538                    .ok_or_else(|| anyhow!("no item builder for project item"))
1539                    .cloned()
1540            })?;
1541            let build_item =
1542                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1543            Ok((project_entry_id, build_item))
1544        })
1545    }
1546
1547    pub fn open_project_item<T>(
1548        &mut self,
1549        project_item: ModelHandle<T::Item>,
1550        cx: &mut ViewContext<Self>,
1551    ) -> ViewHandle<T>
1552    where
1553        T: ProjectItem,
1554    {
1555        use project::Item as _;
1556
1557        let entry_id = project_item.read(cx).entry_id(cx);
1558        if let Some(item) = entry_id
1559            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1560            .and_then(|item| item.downcast())
1561        {
1562            self.activate_item(&item, cx);
1563            return item;
1564        }
1565
1566        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1567        self.add_item(Box::new(item.clone()), cx);
1568        item
1569    }
1570
1571    pub fn open_shared_screen(&mut self, action: &OpenSharedScreen, cx: &mut ViewContext<Self>) {
1572        if let Some(shared_screen) =
1573            self.shared_screen_for_peer(action.peer_id, &self.active_pane, cx)
1574        {
1575            let pane = self.active_pane.clone();
1576            Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
1577        }
1578    }
1579
1580    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1581        let result = self.panes.iter().find_map(|pane| {
1582            pane.read(cx)
1583                .index_for_item(item)
1584                .map(|ix| (pane.clone(), ix))
1585        });
1586        if let Some((pane, ix)) = result {
1587            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1588            true
1589        } else {
1590            false
1591        }
1592    }
1593
1594    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1595        let panes = self.center.panes();
1596        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1597            cx.focus(pane);
1598        } else {
1599            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1600        }
1601    }
1602
1603    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1604        let panes = self.center.panes();
1605        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1606            let next_ix = (ix + 1) % panes.len();
1607            let next_pane = panes[next_ix].clone();
1608            cx.focus(next_pane);
1609        }
1610    }
1611
1612    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1613        let panes = self.center.panes();
1614        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1615            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1616            let prev_pane = panes[prev_ix].clone();
1617            cx.focus(prev_pane);
1618        }
1619    }
1620
1621    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1622        if self.active_pane != pane {
1623            self.active_pane
1624                .update(cx, |pane, cx| pane.set_active(false, cx));
1625            self.active_pane = pane.clone();
1626            self.active_pane
1627                .update(cx, |pane, cx| pane.set_active(true, cx));
1628            self.status_bar.update(cx, |status_bar, cx| {
1629                status_bar.set_active_pane(&self.active_pane, cx);
1630            });
1631            self.active_item_path_changed(cx);
1632
1633            if &pane == self.dock_pane() {
1634                Dock::show(self, true, cx);
1635            } else {
1636                self.last_active_center_pane = Some(pane.downgrade());
1637                if self.dock.is_anchored_at(DockAnchor::Expanded) {
1638                    Dock::hide(self, cx);
1639                }
1640            }
1641            cx.notify();
1642        }
1643
1644        self.update_followers(
1645            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1646                id: self.active_item(cx).and_then(|item| {
1647                    item.to_followable_item_handle(cx)?
1648                        .remote_id(&self.client, cx)
1649                        .map(|id| id.to_proto())
1650                }),
1651                leader_id: self.leader_for_pane(&pane),
1652            }),
1653            cx,
1654        );
1655    }
1656
1657    fn handle_pane_event(
1658        &mut self,
1659        pane_id: usize,
1660        event: &pane::Event,
1661        cx: &mut ViewContext<Self>,
1662    ) {
1663        if let Some(pane) = self.pane(pane_id) {
1664            let is_dock = &pane == self.dock.pane();
1665            match event {
1666                pane::Event::Split(direction) if !is_dock => {
1667                    self.split_pane(pane, *direction, cx);
1668                }
1669                pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
1670                pane::Event::Remove if is_dock => Dock::hide(self, cx),
1671                pane::Event::ActivateItem { local } => {
1672                    if *local {
1673                        self.unfollow(&pane, cx);
1674                    }
1675                    if &pane == self.active_pane() {
1676                        self.active_item_path_changed(cx);
1677                    }
1678                }
1679                pane::Event::ChangeItemTitle => {
1680                    if pane == self.active_pane {
1681                        self.active_item_path_changed(cx);
1682                    }
1683                    self.update_window_edited(cx);
1684                }
1685                pane::Event::RemoveItem { item_id } => {
1686                    self.update_window_edited(cx);
1687                    if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1688                        if entry.get().id() == pane.id() {
1689                            entry.remove();
1690                        }
1691                    }
1692                }
1693                _ => {}
1694            }
1695
1696            self.serialize_workspace(cx);
1697        } else if self.dock.visible_pane().is_none() {
1698            error!("pane {} not found", pane_id);
1699        }
1700    }
1701
1702    pub fn split_pane(
1703        &mut self,
1704        pane: ViewHandle<Pane>,
1705        direction: SplitDirection,
1706        cx: &mut ViewContext<Self>,
1707    ) -> Option<ViewHandle<Pane>> {
1708        if &pane == self.dock_pane() {
1709            warn!("Can't split dock pane.");
1710            return None;
1711        }
1712
1713        let item = pane.read(cx).active_item()?;
1714        let maybe_pane_handle =
1715            if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) {
1716                let new_pane = self.add_pane(cx);
1717                Pane::add_item(self, &new_pane, clone, true, true, None, cx);
1718                self.center.split(&pane, &new_pane, direction).unwrap();
1719                Some(new_pane)
1720            } else {
1721                None
1722            };
1723        cx.notify();
1724        maybe_pane_handle
1725    }
1726
1727    pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext<Self>) {
1728        let Some(pane_to_split) = action.pane_to_split.upgrade(cx) else { return; };
1729        let Some(from) = action.from.upgrade(cx) else { return; };
1730        if &pane_to_split == self.dock_pane() {
1731            warn!("Can't split dock pane.");
1732            return;
1733        }
1734
1735        let new_pane = self.add_pane(cx);
1736        Pane::move_item(
1737            self,
1738            from.clone(),
1739            new_pane.clone(),
1740            action.item_id_to_move,
1741            0,
1742            cx,
1743        );
1744        self.center
1745            .split(&pane_to_split, &new_pane, action.split_direction)
1746            .unwrap();
1747        cx.notify();
1748    }
1749
1750    pub fn split_pane_with_project_entry(
1751        &mut self,
1752        action: &SplitWithProjectEntry,
1753        cx: &mut ViewContext<Self>,
1754    ) -> Option<Task<Result<()>>> {
1755        let pane_to_split = action.pane_to_split.upgrade(cx)?;
1756        if &pane_to_split == self.dock_pane() {
1757            warn!("Can't split dock pane.");
1758            return None;
1759        }
1760
1761        let new_pane = self.add_pane(cx);
1762        self.center
1763            .split(&pane_to_split, &new_pane, action.split_direction)
1764            .unwrap();
1765
1766        let path = self
1767            .project
1768            .read(cx)
1769            .path_for_entry(action.project_entry, cx)?;
1770        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
1771        Some(cx.foreground().spawn(async move {
1772            task.await?;
1773            Ok(())
1774        }))
1775    }
1776
1777    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1778        if self.center.remove(&pane).unwrap() {
1779            self.panes.retain(|p| p != &pane);
1780            cx.focus(self.panes.last().unwrap().clone());
1781            self.unfollow(&pane, cx);
1782            self.last_leaders_by_pane.remove(&pane.downgrade());
1783            for removed_item in pane.read(cx).items() {
1784                self.panes_by_item.remove(&removed_item.id());
1785            }
1786            if self.last_active_center_pane == Some(pane.downgrade()) {
1787                self.last_active_center_pane = None;
1788            }
1789
1790            cx.notify();
1791        } else {
1792            self.active_item_path_changed(cx);
1793        }
1794    }
1795
1796    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1797        &self.panes
1798    }
1799
1800    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1801        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1802    }
1803
1804    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1805        &self.active_pane
1806    }
1807
1808    pub fn dock_pane(&self) -> &ViewHandle<Pane> {
1809        self.dock.pane()
1810    }
1811
1812    fn dock_active(&self) -> bool {
1813        &self.active_pane == self.dock.pane()
1814    }
1815
1816    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1817        if let Some(remote_id) = remote_id {
1818            self.remote_entity_subscription =
1819                Some(self.client.add_view_for_remote_entity(remote_id, cx));
1820        } else {
1821            self.remote_entity_subscription.take();
1822        }
1823    }
1824
1825    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1826        self.leader_state.followers.remove(&peer_id);
1827        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1828            for state in states_by_pane.into_values() {
1829                for item in state.items_by_leader_view_id.into_values() {
1830                    item.set_leader_replica_id(None, cx);
1831                }
1832            }
1833        }
1834        cx.notify();
1835    }
1836
1837    pub fn toggle_follow(
1838        &mut self,
1839        ToggleFollow(leader_id): &ToggleFollow,
1840        cx: &mut ViewContext<Self>,
1841    ) -> Option<Task<Result<()>>> {
1842        let leader_id = *leader_id;
1843        let pane = self.active_pane().clone();
1844
1845        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1846            if leader_id == prev_leader_id {
1847                return None;
1848            }
1849        }
1850
1851        self.last_leaders_by_pane
1852            .insert(pane.downgrade(), leader_id);
1853        self.follower_states_by_leader
1854            .entry(leader_id)
1855            .or_default()
1856            .insert(pane.clone(), Default::default());
1857        cx.notify();
1858
1859        let project_id = self.project.read(cx).remote_id()?;
1860        let request = self.client.request(proto::Follow {
1861            project_id,
1862            leader_id: Some(leader_id),
1863        });
1864
1865        Some(cx.spawn_weak(|this, mut cx| async move {
1866            let response = request.await?;
1867            if let Some(this) = this.upgrade(&cx) {
1868                this.update(&mut cx, |this, _| {
1869                    let state = this
1870                        .follower_states_by_leader
1871                        .get_mut(&leader_id)
1872                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1873                        .ok_or_else(|| anyhow!("following interrupted"))?;
1874                    state.active_view_id = if let Some(active_view_id) = response.active_view_id {
1875                        Some(ViewId::from_proto(active_view_id)?)
1876                    } else {
1877                        None
1878                    };
1879                    Ok::<_, anyhow::Error>(())
1880                })?;
1881                Self::add_views_from_leader(
1882                    this.clone(),
1883                    leader_id,
1884                    vec![pane],
1885                    response.views,
1886                    &mut cx,
1887                )
1888                .await?;
1889                this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx));
1890            }
1891            Ok(())
1892        }))
1893    }
1894
1895    pub fn follow_next_collaborator(
1896        &mut self,
1897        _: &FollowNextCollaborator,
1898        cx: &mut ViewContext<Self>,
1899    ) -> Option<Task<Result<()>>> {
1900        let collaborators = self.project.read(cx).collaborators();
1901        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1902            let mut collaborators = collaborators.keys().copied();
1903            for peer_id in collaborators.by_ref() {
1904                if peer_id == leader_id {
1905                    break;
1906                }
1907            }
1908            collaborators.next()
1909        } else if let Some(last_leader_id) =
1910            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1911        {
1912            if collaborators.contains_key(last_leader_id) {
1913                Some(*last_leader_id)
1914            } else {
1915                None
1916            }
1917        } else {
1918            None
1919        };
1920
1921        next_leader_id
1922            .or_else(|| collaborators.keys().copied().next())
1923            .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
1924    }
1925
1926    pub fn unfollow(
1927        &mut self,
1928        pane: &ViewHandle<Pane>,
1929        cx: &mut ViewContext<Self>,
1930    ) -> Option<PeerId> {
1931        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1932            let leader_id = *leader_id;
1933            if let Some(state) = states_by_pane.remove(pane) {
1934                for (_, item) in state.items_by_leader_view_id {
1935                    item.set_leader_replica_id(None, cx);
1936                }
1937
1938                if states_by_pane.is_empty() {
1939                    self.follower_states_by_leader.remove(&leader_id);
1940                    if let Some(project_id) = self.project.read(cx).remote_id() {
1941                        self.client
1942                            .send(proto::Unfollow {
1943                                project_id,
1944                                leader_id: Some(leader_id),
1945                            })
1946                            .log_err();
1947                    }
1948                }
1949
1950                cx.notify();
1951                return Some(leader_id);
1952            }
1953        }
1954        None
1955    }
1956
1957    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
1958        self.follower_states_by_leader.contains_key(&peer_id)
1959    }
1960
1961    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
1962        self.leader_state.followers.contains(&peer_id)
1963    }
1964
1965    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
1966        // TODO: There should be a better system in place for this
1967        // (https://github.com/zed-industries/zed/issues/1290)
1968        let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
1969        let container_theme = if is_fullscreen {
1970            let mut container_theme = theme.workspace.titlebar.container;
1971            container_theme.padding.left = container_theme.padding.right;
1972            container_theme
1973        } else {
1974            theme.workspace.titlebar.container
1975        };
1976
1977        enum TitleBar {}
1978        ConstrainedBox::new(
1979            MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
1980                Container::new(
1981                    Stack::new()
1982                        .with_children(
1983                            self.titlebar_item
1984                                .as_ref()
1985                                .map(|item| ChildView::new(item, cx).boxed()),
1986                        )
1987                        .boxed(),
1988                )
1989                .with_style(container_theme)
1990                .boxed()
1991            })
1992            .on_click(MouseButton::Left, |event, cx| {
1993                if event.click_count == 2 {
1994                    cx.zoom_window(cx.window_id());
1995                }
1996            })
1997            .boxed(),
1998        )
1999        .with_height(theme.workspace.titlebar.height)
2000        .named("titlebar")
2001    }
2002
2003    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2004        let active_entry = self.active_project_path(cx);
2005        self.project
2006            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2007        self.update_window_title(cx);
2008    }
2009
2010    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2011        let project = self.project().read(cx);
2012        let mut title = String::new();
2013
2014        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2015            let filename = path
2016                .path
2017                .file_name()
2018                .map(|s| s.to_string_lossy())
2019                .or_else(|| {
2020                    Some(Cow::Borrowed(
2021                        project
2022                            .worktree_for_id(path.worktree_id, cx)?
2023                            .read(cx)
2024                            .root_name(),
2025                    ))
2026                });
2027
2028            if let Some(filename) = filename {
2029                title.push_str(filename.as_ref());
2030                title.push_str("");
2031            }
2032        }
2033
2034        for (i, name) in project.worktree_root_names(cx).enumerate() {
2035            if i > 0 {
2036                title.push_str(", ");
2037            }
2038            title.push_str(name);
2039        }
2040
2041        if title.is_empty() {
2042            title = "empty project".to_string();
2043        }
2044
2045        if project.is_remote() {
2046            title.push_str("");
2047        } else if project.is_shared() {
2048            title.push_str("");
2049        }
2050
2051        cx.set_window_title(&title);
2052    }
2053
2054    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2055        let is_edited = !self.project.read(cx).is_read_only()
2056            && self
2057                .items(cx)
2058                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2059        if is_edited != self.window_edited {
2060            self.window_edited = is_edited;
2061            cx.set_window_edited(self.window_edited)
2062        }
2063    }
2064
2065    fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
2066        if self.project.read(cx).is_read_only() {
2067            enum DisconnectedOverlay {}
2068            Some(
2069                MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
2070                    let theme = &cx.global::<Settings>().theme;
2071                    Label::new(
2072                        "Your connection to the remote project has been lost.",
2073                        theme.workspace.disconnected_overlay.text.clone(),
2074                    )
2075                    .aligned()
2076                    .contained()
2077                    .with_style(theme.workspace.disconnected_overlay.container)
2078                    .boxed()
2079                })
2080                .with_cursor_style(CursorStyle::Arrow)
2081                .capture_all()
2082                .boxed(),
2083            )
2084        } else {
2085            None
2086        }
2087    }
2088
2089    fn render_notifications(
2090        &self,
2091        theme: &theme::Workspace,
2092        cx: &AppContext,
2093    ) -> Option<ElementBox> {
2094        if self.notifications.is_empty() {
2095            None
2096        } else {
2097            Some(
2098                Flex::column()
2099                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2100                        ChildView::new(notification.as_ref(), cx)
2101                            .contained()
2102                            .with_style(theme.notification)
2103                            .boxed()
2104                    }))
2105                    .constrained()
2106                    .with_width(theme.notifications.width)
2107                    .contained()
2108                    .with_style(theme.notifications.container)
2109                    .aligned()
2110                    .bottom()
2111                    .right()
2112                    .boxed(),
2113            )
2114        }
2115    }
2116
2117    // RPC handlers
2118
2119    async fn handle_follow(
2120        this: ViewHandle<Self>,
2121        envelope: TypedEnvelope<proto::Follow>,
2122        _: Arc<Client>,
2123        mut cx: AsyncAppContext,
2124    ) -> Result<proto::FollowResponse> {
2125        this.update(&mut cx, |this, cx| {
2126            let client = &this.client;
2127            this.leader_state
2128                .followers
2129                .insert(envelope.original_sender_id()?);
2130
2131            let active_view_id = this.active_item(cx).and_then(|i| {
2132                Some(
2133                    i.to_followable_item_handle(cx)?
2134                        .remote_id(client, cx)?
2135                        .to_proto(),
2136                )
2137            });
2138
2139            cx.notify();
2140
2141            Ok(proto::FollowResponse {
2142                active_view_id,
2143                views: this
2144                    .panes()
2145                    .iter()
2146                    .flat_map(|pane| {
2147                        let leader_id = this.leader_for_pane(pane);
2148                        pane.read(cx).items().filter_map({
2149                            let cx = &cx;
2150                            move |item| {
2151                                let item = item.to_followable_item_handle(cx)?;
2152                                let id = item.remote_id(client, cx)?.to_proto();
2153                                let variant = item.to_state_proto(cx)?;
2154                                Some(proto::View {
2155                                    id: Some(id),
2156                                    leader_id,
2157                                    variant: Some(variant),
2158                                })
2159                            }
2160                        })
2161                    })
2162                    .collect(),
2163            })
2164        })
2165    }
2166
2167    async fn handle_unfollow(
2168        this: ViewHandle<Self>,
2169        envelope: TypedEnvelope<proto::Unfollow>,
2170        _: Arc<Client>,
2171        mut cx: AsyncAppContext,
2172    ) -> Result<()> {
2173        this.update(&mut cx, |this, cx| {
2174            this.leader_state
2175                .followers
2176                .remove(&envelope.original_sender_id()?);
2177            cx.notify();
2178            Ok(())
2179        })
2180    }
2181
2182    async fn handle_update_followers(
2183        this: ViewHandle<Self>,
2184        envelope: TypedEnvelope<proto::UpdateFollowers>,
2185        _: Arc<Client>,
2186        cx: AsyncAppContext,
2187    ) -> Result<()> {
2188        let leader_id = envelope.original_sender_id()?;
2189        this.read_with(&cx, |this, _| {
2190            this.leader_updates_tx
2191                .unbounded_send((leader_id, envelope.payload))
2192        })?;
2193        Ok(())
2194    }
2195
2196    async fn process_leader_update(
2197        this: ViewHandle<Self>,
2198        leader_id: PeerId,
2199        update: proto::UpdateFollowers,
2200        cx: &mut AsyncAppContext,
2201    ) -> Result<()> {
2202        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2203            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2204                this.update(cx, |this, _| {
2205                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2206                        for state in state.values_mut() {
2207                            state.active_view_id =
2208                                if let Some(active_view_id) = update_active_view.id.clone() {
2209                                    Some(ViewId::from_proto(active_view_id)?)
2210                                } else {
2211                                    None
2212                                };
2213                        }
2214                    }
2215                    anyhow::Ok(())
2216                })?;
2217            }
2218            proto::update_followers::Variant::UpdateView(update_view) => {
2219                let variant = update_view
2220                    .variant
2221                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2222                let id = update_view
2223                    .id
2224                    .ok_or_else(|| anyhow!("missing update view id"))?;
2225                let mut tasks = Vec::new();
2226                this.update(cx, |this, cx| {
2227                    let project = this.project.clone();
2228                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2229                        for state in state.values_mut() {
2230                            let view_id = ViewId::from_proto(id.clone())?;
2231                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2232                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2233                            }
2234                        }
2235                    }
2236                    anyhow::Ok(())
2237                })?;
2238                try_join_all(tasks).await.log_err();
2239            }
2240            proto::update_followers::Variant::CreateView(view) => {
2241                let panes = this.read_with(cx, |this, _| {
2242                    this.follower_states_by_leader
2243                        .get(&leader_id)
2244                        .into_iter()
2245                        .flat_map(|states_by_pane| states_by_pane.keys())
2246                        .cloned()
2247                        .collect()
2248                });
2249                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2250            }
2251        }
2252        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2253        Ok(())
2254    }
2255
2256    async fn add_views_from_leader(
2257        this: ViewHandle<Self>,
2258        leader_id: PeerId,
2259        panes: Vec<ViewHandle<Pane>>,
2260        views: Vec<proto::View>,
2261        cx: &mut AsyncAppContext,
2262    ) -> Result<()> {
2263        let project = this.read_with(cx, |this, _| this.project.clone());
2264        let replica_id = project
2265            .read_with(cx, |project, _| {
2266                project
2267                    .collaborators()
2268                    .get(&leader_id)
2269                    .map(|c| c.replica_id)
2270            })
2271            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2272
2273        let item_builders = cx.update(|cx| {
2274            cx.default_global::<FollowableItemBuilders>()
2275                .values()
2276                .map(|b| b.0)
2277                .collect::<Vec<_>>()
2278        });
2279
2280        let mut item_tasks_by_pane = HashMap::default();
2281        for pane in panes {
2282            let mut item_tasks = Vec::new();
2283            let mut leader_view_ids = Vec::new();
2284            for view in &views {
2285                let Some(id) = &view.id else { continue };
2286                let id = ViewId::from_proto(id.clone())?;
2287                let mut variant = view.variant.clone();
2288                if variant.is_none() {
2289                    Err(anyhow!("missing variant"))?;
2290                }
2291                for build_item in &item_builders {
2292                    let task = cx.update(|cx| {
2293                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2294                    });
2295                    if let Some(task) = task {
2296                        item_tasks.push(task);
2297                        leader_view_ids.push(id);
2298                        break;
2299                    } else {
2300                        assert!(variant.is_some());
2301                    }
2302                }
2303            }
2304
2305            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2306        }
2307
2308        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2309            let items = futures::future::try_join_all(item_tasks).await?;
2310            this.update(cx, |this, cx| {
2311                let state = this
2312                    .follower_states_by_leader
2313                    .get_mut(&leader_id)?
2314                    .get_mut(&pane)?;
2315
2316                for (id, item) in leader_view_ids.into_iter().zip(items) {
2317                    item.set_leader_replica_id(Some(replica_id), cx);
2318                    state.items_by_leader_view_id.insert(id, item);
2319                }
2320
2321                Some(())
2322            });
2323        }
2324        Ok(())
2325    }
2326
2327    fn update_followers(
2328        &self,
2329        update: proto::update_followers::Variant,
2330        cx: &AppContext,
2331    ) -> Option<()> {
2332        let project_id = self.project.read(cx).remote_id()?;
2333        if !self.leader_state.followers.is_empty() {
2334            self.client
2335                .send(proto::UpdateFollowers {
2336                    project_id,
2337                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2338                    variant: Some(update),
2339                })
2340                .log_err();
2341        }
2342        None
2343    }
2344
2345    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2346        self.follower_states_by_leader
2347            .iter()
2348            .find_map(|(leader_id, state)| {
2349                if state.contains_key(pane) {
2350                    Some(*leader_id)
2351                } else {
2352                    None
2353                }
2354            })
2355    }
2356
2357    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2358        cx.notify();
2359
2360        let call = self.active_call()?;
2361        let room = call.read(cx).room()?.read(cx);
2362        let participant = room.remote_participant_for_peer_id(leader_id)?;
2363        let mut items_to_activate = Vec::new();
2364        match participant.location {
2365            call::ParticipantLocation::SharedProject { project_id } => {
2366                if Some(project_id) == self.project.read(cx).remote_id() {
2367                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2368                        if let Some(item) = state
2369                            .active_view_id
2370                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2371                        {
2372                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2373                        } else {
2374                            if let Some(shared_screen) =
2375                                self.shared_screen_for_peer(leader_id, pane, cx)
2376                            {
2377                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2378                            }
2379                        }
2380                    }
2381                }
2382            }
2383            call::ParticipantLocation::UnsharedProject => {}
2384            call::ParticipantLocation::External => {
2385                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2386                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2387                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2388                    }
2389                }
2390            }
2391        }
2392
2393        for (pane, item) in items_to_activate {
2394            let active_item_was_focused = pane
2395                .read(cx)
2396                .active_item()
2397                .map(|active_item| cx.is_child_focused(active_item.to_any()))
2398                .unwrap_or_default();
2399
2400            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2401                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2402            } else {
2403                Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2404            }
2405
2406            if active_item_was_focused {
2407                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2408            }
2409        }
2410
2411        None
2412    }
2413
2414    fn shared_screen_for_peer(
2415        &self,
2416        peer_id: PeerId,
2417        pane: &ViewHandle<Pane>,
2418        cx: &mut ViewContext<Self>,
2419    ) -> Option<ViewHandle<SharedScreen>> {
2420        let call = self.active_call()?;
2421        let room = call.read(cx).room()?.read(cx);
2422        let participant = room.remote_participant_for_peer_id(peer_id)?;
2423        let track = participant.tracks.values().next()?.clone();
2424        let user = participant.user.clone();
2425
2426        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2427            if item.read(cx).peer_id == peer_id {
2428                return Some(item);
2429            }
2430        }
2431
2432        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2433    }
2434
2435    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2436        if active {
2437            cx.background()
2438                .spawn(persistence::DB.update_timestamp(self.database_id()))
2439                .detach();
2440        } else {
2441            for pane in &self.panes {
2442                pane.update(cx, |pane, cx| {
2443                    if let Some(item) = pane.active_item() {
2444                        item.workspace_deactivated(cx);
2445                    }
2446                    if matches!(
2447                        cx.global::<Settings>().autosave,
2448                        Autosave::OnWindowChange | Autosave::OnFocusChange
2449                    ) {
2450                        for item in pane.items() {
2451                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2452                                .detach_and_log_err(cx);
2453                        }
2454                    }
2455                });
2456            }
2457        }
2458    }
2459
2460    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2461        self.active_call.as_ref().map(|(call, _)| call)
2462    }
2463
2464    fn on_active_call_event(
2465        &mut self,
2466        _: ModelHandle<ActiveCall>,
2467        event: &call::room::Event,
2468        cx: &mut ViewContext<Self>,
2469    ) {
2470        match event {
2471            call::room::Event::ParticipantLocationChanged { participant_id }
2472            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2473                self.leader_updated(*participant_id, cx);
2474            }
2475            _ => {}
2476        }
2477    }
2478
2479    pub fn database_id(&self) -> WorkspaceId {
2480        self.database_id
2481    }
2482
2483    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2484        let project = self.project().read(cx);
2485
2486        if project.is_local() {
2487            Some(
2488                project
2489                    .visible_worktrees(cx)
2490                    .map(|worktree| worktree.read(cx).abs_path())
2491                    .collect::<Vec<_>>()
2492                    .into(),
2493            )
2494        } else {
2495            None
2496        }
2497    }
2498
2499    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2500        match member {
2501            Member::Axis(PaneAxis { members, .. }) => {
2502                for child in members.iter() {
2503                    self.remove_panes(child.clone(), cx)
2504                }
2505            }
2506            Member::Pane(pane) => self.remove_pane(pane.clone(), cx),
2507        }
2508    }
2509
2510    fn serialize_workspace(&self, cx: &AppContext) {
2511        fn serialize_pane_handle(
2512            pane_handle: &ViewHandle<Pane>,
2513            cx: &AppContext,
2514        ) -> SerializedPane {
2515            let (items, active) = {
2516                let pane = pane_handle.read(cx);
2517                let active_item_id = pane.active_item().map(|item| item.id());
2518                (
2519                    pane.items()
2520                        .filter_map(|item_handle| {
2521                            Some(SerializedItem {
2522                                kind: Arc::from(item_handle.serialized_item_kind()?),
2523                                item_id: item_handle.id(),
2524                                active: Some(item_handle.id()) == active_item_id,
2525                            })
2526                        })
2527                        .collect::<Vec<_>>(),
2528                    pane.is_active(),
2529                )
2530            };
2531
2532            SerializedPane::new(items, active)
2533        }
2534
2535        fn build_serialized_pane_group(
2536            pane_group: &Member,
2537            cx: &AppContext,
2538        ) -> SerializedPaneGroup {
2539            match pane_group {
2540                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2541                    axis: *axis,
2542                    children: members
2543                        .iter()
2544                        .map(|member| build_serialized_pane_group(member, cx))
2545                        .collect::<Vec<_>>(),
2546                },
2547                Member::Pane(pane_handle) => {
2548                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2549                }
2550            }
2551        }
2552
2553        if let Some(location) = self.location(cx) {
2554            // Load bearing special case:
2555            //  - with_local_workspace() relies on this to not have other stuff open
2556            //    when you open your log
2557            if !location.paths().is_empty() {
2558                let dock_pane = serialize_pane_handle(self.dock.pane(), cx);
2559                let center_group = build_serialized_pane_group(&self.center.root, cx);
2560
2561                let serialized_workspace = SerializedWorkspace {
2562                    id: self.database_id,
2563                    location,
2564                    dock_position: self.dock.position(),
2565                    dock_pane,
2566                    center_group,
2567                    left_sidebar_open: self.left_sidebar.read(cx).is_open(),
2568                    bounds: Default::default(),
2569                    display: Default::default(),
2570                };
2571
2572                cx.background()
2573                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2574                    .detach();
2575            }
2576        }
2577    }
2578
2579    fn load_from_serialized_workspace(
2580        workspace: WeakViewHandle<Workspace>,
2581        serialized_workspace: SerializedWorkspace,
2582        cx: &mut MutableAppContext,
2583    ) {
2584        cx.spawn(|mut cx| async move {
2585            if let Some(workspace) = workspace.upgrade(&cx) {
2586                let (project, dock_pane_handle, old_center_pane) =
2587                    workspace.read_with(&cx, |workspace, _| {
2588                        (
2589                            workspace.project().clone(),
2590                            workspace.dock_pane().clone(),
2591                            workspace.last_active_center_pane.clone(),
2592                        )
2593                    });
2594
2595                serialized_workspace
2596                    .dock_pane
2597                    .deserialize_to(
2598                        &project,
2599                        &dock_pane_handle,
2600                        serialized_workspace.id,
2601                        &workspace,
2602                        &mut cx,
2603                    )
2604                    .await;
2605
2606                // Traverse the splits tree and add to things
2607                let center_group = serialized_workspace
2608                    .center_group
2609                    .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2610                    .await;
2611
2612                // Remove old panes from workspace panes list
2613                workspace.update(&mut cx, |workspace, cx| {
2614                    if let Some((center_group, active_pane)) = center_group {
2615                        workspace.remove_panes(workspace.center.root.clone(), cx);
2616
2617                        // Swap workspace center group
2618                        workspace.center = PaneGroup::with_root(center_group);
2619
2620                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
2621                        cx.focus_self();
2622
2623                        if let Some(active_pane) = active_pane {
2624                            cx.focus(active_pane);
2625                        } else {
2626                            cx.focus(workspace.panes.last().unwrap().clone());
2627                        }
2628                    } else {
2629                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
2630                        if let Some(old_center_handle) = old_center_handle {
2631                            cx.focus(old_center_handle)
2632                        } else {
2633                            cx.focus_self()
2634                        }
2635                    }
2636
2637                    if workspace.left_sidebar().read(cx).is_open()
2638                        != serialized_workspace.left_sidebar_open
2639                    {
2640                        workspace.toggle_sidebar(SidebarSide::Left, cx);
2641                    }
2642
2643                    // Note that without after_window, the focus_self() and
2644                    // the focus the dock generates start generating alternating
2645                    // focus due to the deferred execution each triggering each other
2646                    cx.after_window_update(move |workspace, cx| {
2647                        Dock::set_dock_position(
2648                            workspace,
2649                            serialized_workspace.dock_position,
2650                            true,
2651                            cx,
2652                        );
2653                    });
2654
2655                    cx.notify();
2656                });
2657
2658                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
2659                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))
2660            }
2661        })
2662        .detach();
2663    }
2664
2665    #[cfg(any(test, feature = "test-support"))]
2666    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
2667        Self::new(None, 0, project, |_, _| None, || &[], cx)
2668    }
2669}
2670
2671fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAppContext) {
2672    if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
2673        workspace.update(cx, |workspace, cx| {
2674            workspace.show_notification_once(0, cx, |cx| {
2675                cx.add_view(|_| {
2676                    MessageNotification::new(
2677                        indoc::indoc! {"
2678                            Failed to load any database file :(
2679                        "},
2680                        OsOpen("https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml".to_string()),
2681                        "Click to let us know about this error"
2682                    )
2683                })
2684            });
2685        });
2686    } else {
2687        let backup_path = (*db::BACKUP_DB_PATH).read();
2688        if let Some(backup_path) = &*backup_path {
2689            workspace.update(cx, |workspace, cx| {
2690                workspace.show_notification_once(0, cx, |cx| {
2691                    cx.add_view(|_| {
2692                        let backup_path = backup_path.to_string_lossy();
2693                        MessageNotification::new(
2694                            format!(
2695                                indoc::indoc! {"
2696                                Database file was corrupted :(
2697                                Old database backed up to:
2698                                {}
2699                                "},
2700                                backup_path
2701                            ),
2702                            OsOpen(backup_path.to_string()),
2703                            "Click to show old database in finder",
2704                        )
2705                    })
2706                });
2707            });
2708        }
2709    }
2710}
2711
2712impl Entity for Workspace {
2713    type Event = Event;
2714}
2715
2716impl View for Workspace {
2717    fn ui_name() -> &'static str {
2718        "Workspace"
2719    }
2720
2721    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2722        let theme = cx.global::<Settings>().theme.clone();
2723        Stack::new()
2724            .with_child(
2725                Flex::column()
2726                    .with_child(self.render_titlebar(&theme, cx))
2727                    .with_child(
2728                        Stack::new()
2729                            .with_child({
2730                                let project = self.project.clone();
2731                                Flex::row()
2732                                    .with_children(
2733                                        if self.left_sidebar.read(cx).active_item().is_some() {
2734                                            Some(
2735                                                ChildView::new(&self.left_sidebar, cx)
2736                                                    .constrained()
2737                                                    .dynamically(|constraint, cx| {
2738                                                        SizeConstraint::new(
2739                                                            Vector2F::new(20., constraint.min.y()),
2740                                                            Vector2F::new(
2741                                                                cx.window_size.x() * 0.8,
2742                                                                constraint.max.y(),
2743                                                            ),
2744                                                        )
2745                                                    })
2746                                                    .boxed(),
2747                                            )
2748                                        } else {
2749                                            None
2750                                        },
2751                                    )
2752                                    .with_child(
2753                                        FlexItem::new(
2754                                            Flex::column()
2755                                                .with_child(
2756                                                    FlexItem::new(self.center.render(
2757                                                        &project,
2758                                                        &theme,
2759                                                        &self.follower_states_by_leader,
2760                                                        self.active_call(),
2761                                                        self.active_pane(),
2762                                                        cx,
2763                                                    ))
2764                                                    .flex(1., true)
2765                                                    .boxed(),
2766                                                )
2767                                                .with_children(self.dock.render(
2768                                                    &theme,
2769                                                    DockAnchor::Bottom,
2770                                                    cx,
2771                                                ))
2772                                                .boxed(),
2773                                        )
2774                                        .flex(1., true)
2775                                        .boxed(),
2776                                    )
2777                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2778                                    .with_children(
2779                                        if self.right_sidebar.read(cx).active_item().is_some() {
2780                                            Some(
2781                                                ChildView::new(&self.right_sidebar, cx)
2782                                                    .constrained()
2783                                                    .dynamically(|constraint, cx| {
2784                                                        SizeConstraint::new(
2785                                                            Vector2F::new(20., constraint.min.y()),
2786                                                            Vector2F::new(
2787                                                                cx.window_size.x() * 0.8,
2788                                                                constraint.max.y(),
2789                                                            ),
2790                                                        )
2791                                                    })
2792                                                    .boxed(),
2793                                            )
2794                                        } else {
2795                                            None
2796                                        },
2797                                    )
2798                                    .boxed()
2799                            })
2800                            .with_child(
2801                                Overlay::new(
2802                                    Stack::new()
2803                                        .with_children(self.dock.render(
2804                                            &theme,
2805                                            DockAnchor::Expanded,
2806                                            cx,
2807                                        ))
2808                                        .with_children(self.modal.as_ref().map(|modal| {
2809                                            ChildView::new(modal, cx)
2810                                                .contained()
2811                                                .with_style(theme.workspace.modal)
2812                                                .aligned()
2813                                                .top()
2814                                                .boxed()
2815                                        }))
2816                                        .with_children(
2817                                            self.render_notifications(&theme.workspace, cx),
2818                                        )
2819                                        .boxed(),
2820                                )
2821                                .boxed(),
2822                            )
2823                            .flex(1.0, true)
2824                            .boxed(),
2825                    )
2826                    .with_child(ChildView::new(&self.status_bar, cx).boxed())
2827                    .contained()
2828                    .with_background_color(theme.workspace.background)
2829                    .boxed(),
2830            )
2831            .with_children(DragAndDrop::render(cx))
2832            .with_children(self.render_disconnected_overlay(cx))
2833            .named("workspace")
2834    }
2835
2836    fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
2837        if cx.is_self_focused() {
2838            cx.focus(&self.active_pane);
2839        } else {
2840            for pane in self.panes() {
2841                let view = view.clone();
2842                if pane.update(cx, |_, cx| view.id() == cx.view_id() || cx.is_child(view)) {
2843                    self.handle_pane_focused(pane.clone(), cx);
2844                    break;
2845                }
2846            }
2847        }
2848    }
2849
2850    fn keymap_context(&self, _: &AppContext) -> KeymapContext {
2851        Self::default_keymap_context()
2852    }
2853}
2854
2855impl ViewId {
2856    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
2857        Ok(Self {
2858            creator: message
2859                .creator
2860                .ok_or_else(|| anyhow!("creator is missing"))?,
2861            id: message.id,
2862        })
2863    }
2864
2865    pub(crate) fn to_proto(&self) -> proto::ViewId {
2866        proto::ViewId {
2867            creator: Some(self.creator),
2868            id: self.id,
2869        }
2870    }
2871}
2872
2873pub trait WorkspaceHandle {
2874    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2875}
2876
2877impl WorkspaceHandle for ViewHandle<Workspace> {
2878    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2879        self.read(cx)
2880            .worktrees(cx)
2881            .flat_map(|worktree| {
2882                let worktree_id = worktree.read(cx).id();
2883                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2884                    worktree_id,
2885                    path: f.path.clone(),
2886                })
2887            })
2888            .collect::<Vec<_>>()
2889    }
2890}
2891
2892impl std::fmt::Debug for OpenPaths {
2893    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2894        f.debug_struct("OpenPaths")
2895            .field("paths", &self.paths)
2896            .finish()
2897    }
2898}
2899
2900pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2901
2902pub fn activate_workspace_for_project(
2903    cx: &mut MutableAppContext,
2904    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2905) -> Option<ViewHandle<Workspace>> {
2906    for window_id in cx.window_ids().collect::<Vec<_>>() {
2907        if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2908            let project = workspace_handle.read(cx).project.clone();
2909            if project.update(cx, &predicate) {
2910                cx.activate_window(window_id);
2911                return Some(workspace_handle);
2912            }
2913        }
2914    }
2915    None
2916}
2917
2918pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
2919    DB.last_workspace().await.log_err().flatten()
2920}
2921
2922#[allow(clippy::type_complexity)]
2923pub fn open_paths(
2924    abs_paths: &[PathBuf],
2925    app_state: &Arc<AppState>,
2926    requesting_window_id: Option<usize>,
2927    cx: &mut MutableAppContext,
2928) -> Task<(
2929    ViewHandle<Workspace>,
2930    Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
2931)> {
2932    log::info!("open paths {:?}", abs_paths);
2933
2934    // Open paths in existing workspace if possible
2935    let existing =
2936        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2937
2938    let app_state = app_state.clone();
2939    let abs_paths = abs_paths.to_vec();
2940    cx.spawn(|mut cx| async move {
2941        if let Some(existing) = existing {
2942            (
2943                existing.clone(),
2944                existing
2945                    .update(&mut cx, |workspace, cx| {
2946                        workspace.open_paths(abs_paths, true, cx)
2947                    })
2948                    .await,
2949            )
2950        } else {
2951            let contains_directory =
2952                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2953                    .await
2954                    .contains(&false);
2955
2956            cx.update(|cx| {
2957                let task =
2958                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx);
2959
2960                cx.spawn(|mut cx| async move {
2961                    let (workspace, items) = task.await;
2962
2963                    workspace.update(&mut cx, |workspace, cx| {
2964                        if contains_directory {
2965                            workspace.toggle_sidebar(SidebarSide::Left, cx);
2966                        }
2967                    });
2968
2969                    (workspace, items)
2970                })
2971            })
2972            .await
2973        }
2974    })
2975}
2976
2977pub fn open_new(
2978    app_state: &Arc<AppState>,
2979    cx: &mut MutableAppContext,
2980    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
2981) -> Task<()> {
2982    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
2983    cx.spawn(|mut cx| async move {
2984        let (workspace, opened_paths) = task.await;
2985
2986        workspace.update(&mut cx, |workspace, cx| {
2987            if opened_paths.is_empty() {
2988                init(workspace, cx)
2989            }
2990        })
2991    })
2992}
2993
2994fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
2995    let mut parts = value.split(',');
2996    let width: usize = parts.next()?.parse().ok()?;
2997    let height: usize = parts.next()?.parse().ok()?;
2998    Some(vec2f(width as f32, height as f32))
2999}
3000
3001#[cfg(test)]
3002mod tests {
3003    use std::{cell::RefCell, rc::Rc};
3004
3005    use crate::item::test::{TestItem, TestItemEvent, TestProjectItem};
3006
3007    use super::*;
3008    use fs::FakeFs;
3009    use gpui::{executor::Deterministic, TestAppContext};
3010    use project::{Project, ProjectEntryId};
3011    use serde_json::json;
3012
3013    #[gpui::test]
3014    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3015        cx.foreground().forbid_parking();
3016        Settings::test_async(cx);
3017
3018        let fs = FakeFs::new(cx.background());
3019        let project = Project::test(fs, [], cx).await;
3020        let (_, workspace) = cx.add_window(|cx| {
3021            Workspace::new(
3022                Default::default(),
3023                0,
3024                project.clone(),
3025                |_, _| None,
3026                || &[],
3027                cx,
3028            )
3029        });
3030
3031        // Adding an item with no ambiguity renders the tab without detail.
3032        let item1 = cx.add_view(&workspace, |_| {
3033            let mut item = TestItem::new();
3034            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3035            item
3036        });
3037        workspace.update(cx, |workspace, cx| {
3038            workspace.add_item(Box::new(item1.clone()), cx);
3039        });
3040        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3041
3042        // Adding an item that creates ambiguity increases the level of detail on
3043        // both tabs.
3044        let item2 = cx.add_view(&workspace, |_| {
3045            let mut item = TestItem::new();
3046            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3047            item
3048        });
3049        workspace.update(cx, |workspace, cx| {
3050            workspace.add_item(Box::new(item2.clone()), cx);
3051        });
3052        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3053        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3054
3055        // Adding an item that creates ambiguity increases the level of detail only
3056        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3057        // we stop at the highest detail available.
3058        let item3 = cx.add_view(&workspace, |_| {
3059            let mut item = TestItem::new();
3060            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3061            item
3062        });
3063        workspace.update(cx, |workspace, cx| {
3064            workspace.add_item(Box::new(item3.clone()), cx);
3065        });
3066        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3067        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3068        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3069    }
3070
3071    #[gpui::test]
3072    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3073        cx.foreground().forbid_parking();
3074        Settings::test_async(cx);
3075        let fs = FakeFs::new(cx.background());
3076        fs.insert_tree(
3077            "/root1",
3078            json!({
3079                "one.txt": "",
3080                "two.txt": "",
3081            }),
3082        )
3083        .await;
3084        fs.insert_tree(
3085            "/root2",
3086            json!({
3087                "three.txt": "",
3088            }),
3089        )
3090        .await;
3091
3092        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3093        let (window_id, workspace) = cx.add_window(|cx| {
3094            Workspace::new(
3095                Default::default(),
3096                0,
3097                project.clone(),
3098                |_, _| None,
3099                || &[],
3100                cx,
3101            )
3102        });
3103        let worktree_id = project.read_with(cx, |project, cx| {
3104            project.worktrees(cx).next().unwrap().read(cx).id()
3105        });
3106
3107        let item1 = cx.add_view(&workspace, |cx| {
3108            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3109        });
3110        let item2 = cx.add_view(&workspace, |cx| {
3111            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3112        });
3113
3114        // Add an item to an empty pane
3115        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3116        project.read_with(cx, |project, cx| {
3117            assert_eq!(
3118                project.active_entry(),
3119                project
3120                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3121                    .map(|e| e.id)
3122            );
3123        });
3124        assert_eq!(
3125            cx.current_window_title(window_id).as_deref(),
3126            Some("one.txt — root1")
3127        );
3128
3129        // Add a second item to a non-empty pane
3130        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3131        assert_eq!(
3132            cx.current_window_title(window_id).as_deref(),
3133            Some("two.txt — root1")
3134        );
3135        project.read_with(cx, |project, cx| {
3136            assert_eq!(
3137                project.active_entry(),
3138                project
3139                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3140                    .map(|e| e.id)
3141            );
3142        });
3143
3144        // Close the active item
3145        workspace
3146            .update(cx, |workspace, cx| {
3147                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
3148            })
3149            .await
3150            .unwrap();
3151        assert_eq!(
3152            cx.current_window_title(window_id).as_deref(),
3153            Some("one.txt — root1")
3154        );
3155        project.read_with(cx, |project, cx| {
3156            assert_eq!(
3157                project.active_entry(),
3158                project
3159                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3160                    .map(|e| e.id)
3161            );
3162        });
3163
3164        // Add a project folder
3165        project
3166            .update(cx, |project, cx| {
3167                project.find_or_create_local_worktree("/root2", true, cx)
3168            })
3169            .await
3170            .unwrap();
3171        assert_eq!(
3172            cx.current_window_title(window_id).as_deref(),
3173            Some("one.txt — root1, root2")
3174        );
3175
3176        // Remove a project folder
3177        project
3178            .update(cx, |project, cx| project.remove_worktree(worktree_id, cx))
3179            .await;
3180        assert_eq!(
3181            cx.current_window_title(window_id).as_deref(),
3182            Some("one.txt — root2")
3183        );
3184    }
3185
3186    #[gpui::test]
3187    async fn test_close_window(cx: &mut TestAppContext) {
3188        cx.foreground().forbid_parking();
3189        Settings::test_async(cx);
3190        let fs = FakeFs::new(cx.background());
3191        fs.insert_tree("/root", json!({ "one": "" })).await;
3192
3193        let project = Project::test(fs, ["root".as_ref()], cx).await;
3194        let (window_id, workspace) = cx.add_window(|cx| {
3195            Workspace::new(
3196                Default::default(),
3197                0,
3198                project.clone(),
3199                |_, _| None,
3200                || &[],
3201                cx,
3202            )
3203        });
3204
3205        // When there are no dirty items, there's nothing to do.
3206        let item1 = cx.add_view(&workspace, |_| TestItem::new());
3207        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3208        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3209        assert!(task.await.unwrap());
3210
3211        // When there are dirty untitled items, prompt to save each one. If the user
3212        // cancels any prompt, then abort.
3213        let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true));
3214        let item3 = cx.add_view(&workspace, |cx| {
3215            TestItem::new()
3216                .with_dirty(true)
3217                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3218        });
3219        workspace.update(cx, |w, cx| {
3220            w.add_item(Box::new(item2.clone()), cx);
3221            w.add_item(Box::new(item3.clone()), cx);
3222        });
3223        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3224        cx.foreground().run_until_parked();
3225        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3226        cx.foreground().run_until_parked();
3227        assert!(!cx.has_pending_prompt(window_id));
3228        assert!(!task.await.unwrap());
3229    }
3230
3231    #[gpui::test]
3232    async fn test_close_pane_items(cx: &mut TestAppContext) {
3233        cx.foreground().forbid_parking();
3234        Settings::test_async(cx);
3235        let fs = FakeFs::new(cx.background());
3236
3237        let project = Project::test(fs, None, cx).await;
3238        let (window_id, workspace) = cx.add_window(|cx| {
3239            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3240        });
3241
3242        let item1 = cx.add_view(&workspace, |cx| {
3243            TestItem::new()
3244                .with_dirty(true)
3245                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3246        });
3247        let item2 = cx.add_view(&workspace, |cx| {
3248            TestItem::new()
3249                .with_dirty(true)
3250                .with_conflict(true)
3251                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3252        });
3253        let item3 = cx.add_view(&workspace, |cx| {
3254            TestItem::new()
3255                .with_dirty(true)
3256                .with_conflict(true)
3257                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3258        });
3259        let item4 = cx.add_view(&workspace, |cx| {
3260            TestItem::new()
3261                .with_dirty(true)
3262                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3263        });
3264        let pane = workspace.update(cx, |workspace, cx| {
3265            workspace.add_item(Box::new(item1.clone()), cx);
3266            workspace.add_item(Box::new(item2.clone()), cx);
3267            workspace.add_item(Box::new(item3.clone()), cx);
3268            workspace.add_item(Box::new(item4.clone()), cx);
3269            workspace.active_pane().clone()
3270        });
3271
3272        let close_items = workspace.update(cx, |workspace, cx| {
3273            pane.update(cx, |pane, cx| {
3274                pane.activate_item(1, true, true, cx);
3275                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3276            });
3277
3278            let item1_id = item1.id();
3279            let item3_id = item3.id();
3280            let item4_id = item4.id();
3281            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3282                [item1_id, item3_id, item4_id].contains(&id)
3283            })
3284        });
3285        cx.foreground().run_until_parked();
3286
3287        // There's a prompt to save item 1.
3288        pane.read_with(cx, |pane, _| {
3289            assert_eq!(pane.items_len(), 4);
3290            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3291        });
3292        assert!(cx.has_pending_prompt(window_id));
3293
3294        // Confirm saving item 1.
3295        cx.simulate_prompt_answer(window_id, 0);
3296        cx.foreground().run_until_parked();
3297
3298        // Item 1 is saved. There's a prompt to save item 3.
3299        pane.read_with(cx, |pane, cx| {
3300            assert_eq!(item1.read(cx).save_count, 1);
3301            assert_eq!(item1.read(cx).save_as_count, 0);
3302            assert_eq!(item1.read(cx).reload_count, 0);
3303            assert_eq!(pane.items_len(), 3);
3304            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3305        });
3306        assert!(cx.has_pending_prompt(window_id));
3307
3308        // Cancel saving item 3.
3309        cx.simulate_prompt_answer(window_id, 1);
3310        cx.foreground().run_until_parked();
3311
3312        // Item 3 is reloaded. There's a prompt to save item 4.
3313        pane.read_with(cx, |pane, cx| {
3314            assert_eq!(item3.read(cx).save_count, 0);
3315            assert_eq!(item3.read(cx).save_as_count, 0);
3316            assert_eq!(item3.read(cx).reload_count, 1);
3317            assert_eq!(pane.items_len(), 2);
3318            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3319        });
3320        assert!(cx.has_pending_prompt(window_id));
3321
3322        // Confirm saving item 4.
3323        cx.simulate_prompt_answer(window_id, 0);
3324        cx.foreground().run_until_parked();
3325
3326        // There's a prompt for a path for item 4.
3327        cx.simulate_new_path_selection(|_| Some(Default::default()));
3328        close_items.await.unwrap();
3329
3330        // The requested items are closed.
3331        pane.read_with(cx, |pane, cx| {
3332            assert_eq!(item4.read(cx).save_count, 0);
3333            assert_eq!(item4.read(cx).save_as_count, 1);
3334            assert_eq!(item4.read(cx).reload_count, 0);
3335            assert_eq!(pane.items_len(), 1);
3336            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3337        });
3338    }
3339
3340    #[gpui::test]
3341    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3342        cx.foreground().forbid_parking();
3343        Settings::test_async(cx);
3344        let fs = FakeFs::new(cx.background());
3345
3346        let project = Project::test(fs, [], cx).await;
3347        let (window_id, workspace) = cx.add_window(|cx| {
3348            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3349        });
3350
3351        // Create several workspace items with single project entries, and two
3352        // workspace items with multiple project entries.
3353        let single_entry_items = (0..=4)
3354            .map(|project_entry_id| {
3355                cx.add_view(&workspace, |cx| {
3356                    TestItem::new()
3357                        .with_dirty(true)
3358                        .with_project_items(&[TestProjectItem::new(
3359                            project_entry_id,
3360                            &format!("{project_entry_id}.txt"),
3361                            cx,
3362                        )])
3363                })
3364            })
3365            .collect::<Vec<_>>();
3366        let item_2_3 = cx.add_view(&workspace, |cx| {
3367            TestItem::new()
3368                .with_dirty(true)
3369                .with_singleton(false)
3370                .with_project_items(&[
3371                    single_entry_items[2].read(cx).project_items[0].clone(),
3372                    single_entry_items[3].read(cx).project_items[0].clone(),
3373                ])
3374        });
3375        let item_3_4 = cx.add_view(&workspace, |cx| {
3376            TestItem::new()
3377                .with_dirty(true)
3378                .with_singleton(false)
3379                .with_project_items(&[
3380                    single_entry_items[3].read(cx).project_items[0].clone(),
3381                    single_entry_items[4].read(cx).project_items[0].clone(),
3382                ])
3383        });
3384
3385        // Create two panes that contain the following project entries:
3386        //   left pane:
3387        //     multi-entry items:   (2, 3)
3388        //     single-entry items:  0, 1, 2, 3, 4
3389        //   right pane:
3390        //     single-entry items:  1
3391        //     multi-entry items:   (3, 4)
3392        let left_pane = workspace.update(cx, |workspace, cx| {
3393            let left_pane = workspace.active_pane().clone();
3394            workspace.add_item(Box::new(item_2_3.clone()), cx);
3395            for item in single_entry_items {
3396                workspace.add_item(Box::new(item), cx);
3397            }
3398            left_pane.update(cx, |pane, cx| {
3399                pane.activate_item(2, true, true, cx);
3400            });
3401
3402            workspace
3403                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3404                .unwrap();
3405
3406            left_pane
3407        });
3408
3409        //Need to cause an effect flush in order to respect new focus
3410        workspace.update(cx, |workspace, cx| {
3411            workspace.add_item(Box::new(item_3_4.clone()), cx);
3412            cx.focus(left_pane.clone());
3413        });
3414
3415        // When closing all of the items in the left pane, we should be prompted twice:
3416        // once for project entry 0, and once for project entry 2. After those two
3417        // prompts, the task should complete.
3418
3419        let close = workspace.update(cx, |workspace, cx| {
3420            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3421        });
3422
3423        cx.foreground().run_until_parked();
3424        left_pane.read_with(cx, |pane, cx| {
3425            assert_eq!(
3426                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3427                &[ProjectEntryId::from_proto(0)]
3428            );
3429        });
3430        cx.simulate_prompt_answer(window_id, 0);
3431
3432        cx.foreground().run_until_parked();
3433        left_pane.read_with(cx, |pane, cx| {
3434            assert_eq!(
3435                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3436                &[ProjectEntryId::from_proto(2)]
3437            );
3438        });
3439        cx.simulate_prompt_answer(window_id, 0);
3440
3441        cx.foreground().run_until_parked();
3442        close.await.unwrap();
3443        left_pane.read_with(cx, |pane, _| {
3444            assert_eq!(pane.items_len(), 0);
3445        });
3446    }
3447
3448    #[gpui::test]
3449    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3450        deterministic.forbid_parking();
3451
3452        Settings::test_async(cx);
3453        let fs = FakeFs::new(cx.background());
3454
3455        let project = Project::test(fs, [], cx).await;
3456        let (window_id, workspace) = cx.add_window(|cx| {
3457            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3458        });
3459
3460        let item = cx.add_view(&workspace, |cx| {
3461            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3462        });
3463        let item_id = item.id();
3464        workspace.update(cx, |workspace, cx| {
3465            workspace.add_item(Box::new(item.clone()), cx);
3466        });
3467
3468        // Autosave on window change.
3469        item.update(cx, |item, cx| {
3470            cx.update_global(|settings: &mut Settings, _| {
3471                settings.autosave = Autosave::OnWindowChange;
3472            });
3473            item.is_dirty = true;
3474        });
3475
3476        // Deactivating the window saves the file.
3477        cx.simulate_window_activation(None);
3478        deterministic.run_until_parked();
3479        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3480
3481        // Autosave on focus change.
3482        item.update(cx, |item, cx| {
3483            cx.focus_self();
3484            cx.update_global(|settings: &mut Settings, _| {
3485                settings.autosave = Autosave::OnFocusChange;
3486            });
3487            item.is_dirty = true;
3488        });
3489
3490        // Blurring the item saves the file.
3491        item.update(cx, |_, cx| cx.blur());
3492        deterministic.run_until_parked();
3493        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3494
3495        // Deactivating the window still saves the file.
3496        cx.simulate_window_activation(Some(window_id));
3497        item.update(cx, |item, cx| {
3498            cx.focus_self();
3499            item.is_dirty = true;
3500        });
3501        cx.simulate_window_activation(None);
3502
3503        deterministic.run_until_parked();
3504        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3505
3506        // Autosave after delay.
3507        item.update(cx, |item, cx| {
3508            cx.update_global(|settings: &mut Settings, _| {
3509                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3510            });
3511            item.is_dirty = true;
3512            cx.emit(TestItemEvent::Edit);
3513        });
3514
3515        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3516        deterministic.advance_clock(Duration::from_millis(250));
3517        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3518
3519        // After delay expires, the file is saved.
3520        deterministic.advance_clock(Duration::from_millis(250));
3521        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3522
3523        // Autosave on focus change, ensuring closing the tab counts as such.
3524        item.update(cx, |item, cx| {
3525            cx.update_global(|settings: &mut Settings, _| {
3526                settings.autosave = Autosave::OnFocusChange;
3527            });
3528            item.is_dirty = true;
3529        });
3530
3531        workspace
3532            .update(cx, |workspace, cx| {
3533                let pane = workspace.active_pane().clone();
3534                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3535            })
3536            .await
3537            .unwrap();
3538        assert!(!cx.has_pending_prompt(window_id));
3539        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3540
3541        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3542        workspace.update(cx, |workspace, cx| {
3543            workspace.add_item(Box::new(item.clone()), cx);
3544        });
3545        item.update(cx, |item, cx| {
3546            item.project_items[0].update(cx, |item, _| {
3547                item.entry_id = None;
3548            });
3549            item.is_dirty = true;
3550            cx.blur();
3551        });
3552        deterministic.run_until_parked();
3553        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3554
3555        // Ensure autosave is prevented for deleted files also when closing the buffer.
3556        let _close_items = workspace.update(cx, |workspace, cx| {
3557            let pane = workspace.active_pane().clone();
3558            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3559        });
3560        deterministic.run_until_parked();
3561        assert!(cx.has_pending_prompt(window_id));
3562        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3563    }
3564
3565    #[gpui::test]
3566    async fn test_pane_navigation(
3567        deterministic: Arc<Deterministic>,
3568        cx: &mut gpui::TestAppContext,
3569    ) {
3570        deterministic.forbid_parking();
3571        Settings::test_async(cx);
3572        let fs = FakeFs::new(cx.background());
3573
3574        let project = Project::test(fs, [], cx).await;
3575        let (_, workspace) = cx.add_window(|cx| {
3576            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3577        });
3578
3579        let item = cx.add_view(&workspace, |cx| {
3580            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3581        });
3582        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3583        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3584        let toolbar_notify_count = Rc::new(RefCell::new(0));
3585
3586        workspace.update(cx, |workspace, cx| {
3587            workspace.add_item(Box::new(item.clone()), cx);
3588            let toolbar_notification_count = toolbar_notify_count.clone();
3589            cx.observe(&toolbar, move |_, _, _| {
3590                *toolbar_notification_count.borrow_mut() += 1
3591            })
3592            .detach();
3593        });
3594
3595        pane.read_with(cx, |pane, _| {
3596            assert!(!pane.can_navigate_backward());
3597            assert!(!pane.can_navigate_forward());
3598        });
3599
3600        item.update(cx, |item, cx| {
3601            item.set_state("one".to_string(), cx);
3602        });
3603
3604        // Toolbar must be notified to re-render the navigation buttons
3605        assert_eq!(*toolbar_notify_count.borrow(), 1);
3606
3607        pane.read_with(cx, |pane, _| {
3608            assert!(pane.can_navigate_backward());
3609            assert!(!pane.can_navigate_forward());
3610        });
3611
3612        workspace
3613            .update(cx, |workspace, cx| {
3614                Pane::go_back(workspace, Some(pane.clone()), cx)
3615            })
3616            .await;
3617
3618        assert_eq!(*toolbar_notify_count.borrow(), 3);
3619        pane.read_with(cx, |pane, _| {
3620            assert!(!pane.can_navigate_backward());
3621            assert!(pane.can_navigate_forward());
3622        });
3623    }
3624}