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