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