workspace.rs

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