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