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