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