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