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