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