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