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