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