workspace.rs

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