workspace.rs

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