workspace.rs

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