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