workspace.rs

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