workspace.rs

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