workspace.rs

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