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, true, 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.panes.retain(|p| p != &pane);
1747            cx.focus(self.panes.last().unwrap());
1748            self.unfollow(&pane, cx);
1749            self.last_leaders_by_pane.remove(&pane.downgrade());
1750            for removed_item in pane.read(cx).items() {
1751                self.panes_by_item.remove(&removed_item.id());
1752            }
1753            if self.last_active_center_pane == Some(pane.downgrade()) {
1754                self.last_active_center_pane = None;
1755            }
1756
1757            cx.notify();
1758        } else {
1759            self.active_item_path_changed(cx);
1760        }
1761    }
1762
1763    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1764        &self.panes
1765    }
1766
1767    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1768        &self.active_pane
1769    }
1770
1771    pub fn dock_pane(&self) -> &ViewHandle<Pane> {
1772        self.dock.pane()
1773    }
1774
1775    fn dock_active(&self) -> bool {
1776        &self.active_pane == self.dock.pane()
1777    }
1778
1779    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1780        if let Some(remote_id) = remote_id {
1781            self.remote_entity_subscription = Some(
1782                self.app_state
1783                    .client
1784                    .add_view_for_remote_entity(remote_id, cx),
1785            );
1786        } else {
1787            self.remote_entity_subscription.take();
1788        }
1789    }
1790
1791    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1792        self.leader_state.followers.remove(&peer_id);
1793        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1794            for state in states_by_pane.into_values() {
1795                for item in state.items_by_leader_view_id.into_values() {
1796                    item.set_leader_replica_id(None, cx);
1797                }
1798            }
1799        }
1800        cx.notify();
1801    }
1802
1803    pub fn toggle_follow(
1804        &mut self,
1805        leader_id: PeerId,
1806        cx: &mut ViewContext<Self>,
1807    ) -> Option<Task<Result<()>>> {
1808        let pane = self.active_pane().clone();
1809
1810        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1811            if leader_id == prev_leader_id {
1812                return None;
1813            }
1814        }
1815
1816        self.last_leaders_by_pane
1817            .insert(pane.downgrade(), leader_id);
1818        self.follower_states_by_leader
1819            .entry(leader_id)
1820            .or_default()
1821            .insert(pane.clone(), Default::default());
1822        cx.notify();
1823
1824        let project_id = self.project.read(cx).remote_id()?;
1825        let request = self.app_state.client.request(proto::Follow {
1826            project_id,
1827            leader_id: Some(leader_id),
1828        });
1829
1830        Some(cx.spawn(|this, mut cx| async move {
1831            let response = request.await?;
1832            this.update(&mut cx, |this, _| {
1833                let state = this
1834                    .follower_states_by_leader
1835                    .get_mut(&leader_id)
1836                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
1837                    .ok_or_else(|| anyhow!("following interrupted"))?;
1838                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
1839                    Some(ViewId::from_proto(active_view_id)?)
1840                } else {
1841                    None
1842                };
1843                Ok::<_, anyhow::Error>(())
1844            })??;
1845            Self::add_views_from_leader(
1846                this.clone(),
1847                leader_id,
1848                vec![pane],
1849                response.views,
1850                &mut cx,
1851            )
1852            .await?;
1853            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
1854            Ok(())
1855        }))
1856    }
1857
1858    pub fn follow_next_collaborator(
1859        &mut self,
1860        _: &FollowNextCollaborator,
1861        cx: &mut ViewContext<Self>,
1862    ) -> Option<Task<Result<()>>> {
1863        let collaborators = self.project.read(cx).collaborators();
1864        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
1865            let mut collaborators = collaborators.keys().copied();
1866            for peer_id in collaborators.by_ref() {
1867                if peer_id == leader_id {
1868                    break;
1869                }
1870            }
1871            collaborators.next()
1872        } else if let Some(last_leader_id) =
1873            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
1874        {
1875            if collaborators.contains_key(last_leader_id) {
1876                Some(*last_leader_id)
1877            } else {
1878                None
1879            }
1880        } else {
1881            None
1882        };
1883
1884        next_leader_id
1885            .or_else(|| collaborators.keys().copied().next())
1886            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
1887    }
1888
1889    pub fn unfollow(
1890        &mut self,
1891        pane: &ViewHandle<Pane>,
1892        cx: &mut ViewContext<Self>,
1893    ) -> Option<PeerId> {
1894        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
1895            let leader_id = *leader_id;
1896            if let Some(state) = states_by_pane.remove(pane) {
1897                for (_, item) in state.items_by_leader_view_id {
1898                    item.set_leader_replica_id(None, cx);
1899                }
1900
1901                if states_by_pane.is_empty() {
1902                    self.follower_states_by_leader.remove(&leader_id);
1903                    if let Some(project_id) = self.project.read(cx).remote_id() {
1904                        self.app_state
1905                            .client
1906                            .send(proto::Unfollow {
1907                                project_id,
1908                                leader_id: Some(leader_id),
1909                            })
1910                            .log_err();
1911                    }
1912                }
1913
1914                cx.notify();
1915                return Some(leader_id);
1916            }
1917        }
1918        None
1919    }
1920
1921    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
1922        self.follower_states_by_leader.contains_key(&peer_id)
1923    }
1924
1925    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
1926        self.leader_state.followers.contains(&peer_id)
1927    }
1928
1929    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
1930        // TODO: There should be a better system in place for this
1931        // (https://github.com/zed-industries/zed/issues/1290)
1932        let is_fullscreen = cx.window_is_fullscreen();
1933        let container_theme = if is_fullscreen {
1934            let mut container_theme = theme.workspace.titlebar.container;
1935            container_theme.padding.left = container_theme.padding.right;
1936            container_theme
1937        } else {
1938            theme.workspace.titlebar.container
1939        };
1940
1941        enum TitleBar {}
1942        MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
1943            Stack::new()
1944                .with_children(
1945                    self.titlebar_item
1946                        .as_ref()
1947                        .map(|item| ChildView::new(item, cx)),
1948                )
1949                .contained()
1950                .with_style(container_theme)
1951        })
1952        .on_click(MouseButton::Left, |event, _, cx| {
1953            if event.click_count == 2 {
1954                cx.zoom_window();
1955            }
1956        })
1957        .constrained()
1958        .with_height(theme.workspace.titlebar.height)
1959        .into_any_named("titlebar")
1960    }
1961
1962    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
1963        let active_entry = self.active_project_path(cx);
1964        self.project
1965            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
1966        self.update_window_title(cx);
1967    }
1968
1969    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
1970        let project = self.project().read(cx);
1971        let mut title = String::new();
1972
1973        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
1974            let filename = path
1975                .path
1976                .file_name()
1977                .map(|s| s.to_string_lossy())
1978                .or_else(|| {
1979                    Some(Cow::Borrowed(
1980                        project
1981                            .worktree_for_id(path.worktree_id, cx)?
1982                            .read(cx)
1983                            .root_name(),
1984                    ))
1985                });
1986
1987            if let Some(filename) = filename {
1988                title.push_str(filename.as_ref());
1989                title.push_str("");
1990            }
1991        }
1992
1993        for (i, name) in project.worktree_root_names(cx).enumerate() {
1994            if i > 0 {
1995                title.push_str(", ");
1996            }
1997            title.push_str(name);
1998        }
1999
2000        if title.is_empty() {
2001            title = "empty project".to_string();
2002        }
2003
2004        if project.is_remote() {
2005            title.push_str("");
2006        } else if project.is_shared() {
2007            title.push_str("");
2008        }
2009
2010        cx.set_window_title(&title);
2011    }
2012
2013    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2014        let is_edited = !self.project.read(cx).is_read_only()
2015            && self
2016                .items(cx)
2017                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2018        if is_edited != self.window_edited {
2019            self.window_edited = is_edited;
2020            cx.set_window_edited(self.window_edited)
2021        }
2022    }
2023
2024    fn render_disconnected_overlay(
2025        &self,
2026        cx: &mut ViewContext<Workspace>,
2027    ) -> Option<AnyElement<Workspace>> {
2028        if self.project.read(cx).is_read_only() {
2029            enum DisconnectedOverlay {}
2030            Some(
2031                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
2032                    let theme = &cx.global::<Settings>().theme;
2033                    Label::new(
2034                        "Your connection to the remote project has been lost.",
2035                        theme.workspace.disconnected_overlay.text.clone(),
2036                    )
2037                    .aligned()
2038                    .contained()
2039                    .with_style(theme.workspace.disconnected_overlay.container)
2040                })
2041                .with_cursor_style(CursorStyle::Arrow)
2042                .capture_all()
2043                .into_any_named("disconnected overlay"),
2044            )
2045        } else {
2046            None
2047        }
2048    }
2049
2050    fn render_notifications(
2051        &self,
2052        theme: &theme::Workspace,
2053        cx: &AppContext,
2054    ) -> Option<AnyElement<Workspace>> {
2055        if self.notifications.is_empty() {
2056            None
2057        } else {
2058            Some(
2059                Flex::column()
2060                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2061                        ChildView::new(notification.as_any(), cx)
2062                            .contained()
2063                            .with_style(theme.notification)
2064                    }))
2065                    .constrained()
2066                    .with_width(theme.notifications.width)
2067                    .contained()
2068                    .with_style(theme.notifications.container)
2069                    .aligned()
2070                    .bottom()
2071                    .right()
2072                    .into_any(),
2073            )
2074        }
2075    }
2076
2077    // RPC handlers
2078
2079    async fn handle_follow(
2080        this: WeakViewHandle<Self>,
2081        envelope: TypedEnvelope<proto::Follow>,
2082        _: Arc<Client>,
2083        mut cx: AsyncAppContext,
2084    ) -> Result<proto::FollowResponse> {
2085        this.update(&mut cx, |this, cx| {
2086            let client = &this.app_state.client;
2087            this.leader_state
2088                .followers
2089                .insert(envelope.original_sender_id()?);
2090
2091            let active_view_id = this.active_item(cx).and_then(|i| {
2092                Some(
2093                    i.to_followable_item_handle(cx)?
2094                        .remote_id(client, cx)?
2095                        .to_proto(),
2096                )
2097            });
2098
2099            cx.notify();
2100
2101            Ok(proto::FollowResponse {
2102                active_view_id,
2103                views: this
2104                    .panes()
2105                    .iter()
2106                    .flat_map(|pane| {
2107                        let leader_id = this.leader_for_pane(pane);
2108                        pane.read(cx).items().filter_map({
2109                            let cx = &cx;
2110                            move |item| {
2111                                let item = item.to_followable_item_handle(cx)?;
2112                                let id = item.remote_id(client, cx)?.to_proto();
2113                                let variant = item.to_state_proto(cx)?;
2114                                Some(proto::View {
2115                                    id: Some(id),
2116                                    leader_id,
2117                                    variant: Some(variant),
2118                                })
2119                            }
2120                        })
2121                    })
2122                    .collect(),
2123            })
2124        })?
2125    }
2126
2127    async fn handle_unfollow(
2128        this: WeakViewHandle<Self>,
2129        envelope: TypedEnvelope<proto::Unfollow>,
2130        _: Arc<Client>,
2131        mut cx: AsyncAppContext,
2132    ) -> Result<()> {
2133        this.update(&mut cx, |this, cx| {
2134            this.leader_state
2135                .followers
2136                .remove(&envelope.original_sender_id()?);
2137            cx.notify();
2138            Ok(())
2139        })?
2140    }
2141
2142    async fn handle_update_followers(
2143        this: WeakViewHandle<Self>,
2144        envelope: TypedEnvelope<proto::UpdateFollowers>,
2145        _: Arc<Client>,
2146        cx: AsyncAppContext,
2147    ) -> Result<()> {
2148        let leader_id = envelope.original_sender_id()?;
2149        this.read_with(&cx, |this, _| {
2150            this.leader_updates_tx
2151                .unbounded_send((leader_id, envelope.payload))
2152        })??;
2153        Ok(())
2154    }
2155
2156    async fn process_leader_update(
2157        this: &WeakViewHandle<Self>,
2158        leader_id: PeerId,
2159        update: proto::UpdateFollowers,
2160        cx: &mut AsyncAppContext,
2161    ) -> Result<()> {
2162        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2163            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2164                this.update(cx, |this, _| {
2165                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2166                        for state in state.values_mut() {
2167                            state.active_view_id =
2168                                if let Some(active_view_id) = update_active_view.id.clone() {
2169                                    Some(ViewId::from_proto(active_view_id)?)
2170                                } else {
2171                                    None
2172                                };
2173                        }
2174                    }
2175                    anyhow::Ok(())
2176                })??;
2177            }
2178            proto::update_followers::Variant::UpdateView(update_view) => {
2179                let variant = update_view
2180                    .variant
2181                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2182                let id = update_view
2183                    .id
2184                    .ok_or_else(|| anyhow!("missing update view id"))?;
2185                let mut tasks = Vec::new();
2186                this.update(cx, |this, cx| {
2187                    let project = this.project.clone();
2188                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2189                        for state in state.values_mut() {
2190                            let view_id = ViewId::from_proto(id.clone())?;
2191                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2192                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2193                            }
2194                        }
2195                    }
2196                    anyhow::Ok(())
2197                })??;
2198                try_join_all(tasks).await.log_err();
2199            }
2200            proto::update_followers::Variant::CreateView(view) => {
2201                let panes = this.read_with(cx, |this, _| {
2202                    this.follower_states_by_leader
2203                        .get(&leader_id)
2204                        .into_iter()
2205                        .flat_map(|states_by_pane| states_by_pane.keys())
2206                        .cloned()
2207                        .collect()
2208                })?;
2209                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2210            }
2211        }
2212        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2213        Ok(())
2214    }
2215
2216    async fn add_views_from_leader(
2217        this: WeakViewHandle<Self>,
2218        leader_id: PeerId,
2219        panes: Vec<ViewHandle<Pane>>,
2220        views: Vec<proto::View>,
2221        cx: &mut AsyncAppContext,
2222    ) -> Result<()> {
2223        let project = this.read_with(cx, |this, _| this.project.clone())?;
2224        let replica_id = project
2225            .read_with(cx, |project, _| {
2226                project
2227                    .collaborators()
2228                    .get(&leader_id)
2229                    .map(|c| c.replica_id)
2230            })
2231            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2232
2233        let item_builders = cx.update(|cx| {
2234            cx.default_global::<FollowableItemBuilders>()
2235                .values()
2236                .map(|b| b.0)
2237                .collect::<Vec<_>>()
2238        });
2239
2240        let mut item_tasks_by_pane = HashMap::default();
2241        for pane in panes {
2242            let mut item_tasks = Vec::new();
2243            let mut leader_view_ids = Vec::new();
2244            for view in &views {
2245                let Some(id) = &view.id else { continue };
2246                let id = ViewId::from_proto(id.clone())?;
2247                let mut variant = view.variant.clone();
2248                if variant.is_none() {
2249                    Err(anyhow!("missing variant"))?;
2250                }
2251                for build_item in &item_builders {
2252                    let task = cx.update(|cx| {
2253                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2254                    });
2255                    if let Some(task) = task {
2256                        item_tasks.push(task);
2257                        leader_view_ids.push(id);
2258                        break;
2259                    } else {
2260                        assert!(variant.is_some());
2261                    }
2262                }
2263            }
2264
2265            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2266        }
2267
2268        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2269            let items = futures::future::try_join_all(item_tasks).await?;
2270            this.update(cx, |this, cx| {
2271                let state = this
2272                    .follower_states_by_leader
2273                    .get_mut(&leader_id)?
2274                    .get_mut(&pane)?;
2275
2276                for (id, item) in leader_view_ids.into_iter().zip(items) {
2277                    item.set_leader_replica_id(Some(replica_id), cx);
2278                    state.items_by_leader_view_id.insert(id, item);
2279                }
2280
2281                Some(())
2282            })?;
2283        }
2284        Ok(())
2285    }
2286
2287    fn update_followers(
2288        &self,
2289        update: proto::update_followers::Variant,
2290        cx: &AppContext,
2291    ) -> Option<()> {
2292        let project_id = self.project.read(cx).remote_id()?;
2293        if !self.leader_state.followers.is_empty() {
2294            self.app_state
2295                .client
2296                .send(proto::UpdateFollowers {
2297                    project_id,
2298                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2299                    variant: Some(update),
2300                })
2301                .log_err();
2302        }
2303        None
2304    }
2305
2306    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2307        self.follower_states_by_leader
2308            .iter()
2309            .find_map(|(leader_id, state)| {
2310                if state.contains_key(pane) {
2311                    Some(*leader_id)
2312                } else {
2313                    None
2314                }
2315            })
2316    }
2317
2318    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2319        cx.notify();
2320
2321        let call = self.active_call()?;
2322        let room = call.read(cx).room()?.read(cx);
2323        let participant = room.remote_participant_for_peer_id(leader_id)?;
2324        let mut items_to_activate = Vec::new();
2325        match participant.location {
2326            call::ParticipantLocation::SharedProject { project_id } => {
2327                if Some(project_id) == self.project.read(cx).remote_id() {
2328                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2329                        if let Some(item) = state
2330                            .active_view_id
2331                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2332                        {
2333                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2334                        } else {
2335                            if let Some(shared_screen) =
2336                                self.shared_screen_for_peer(leader_id, pane, cx)
2337                            {
2338                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2339                            }
2340                        }
2341                    }
2342                }
2343            }
2344            call::ParticipantLocation::UnsharedProject => {}
2345            call::ParticipantLocation::External => {
2346                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2347                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2348                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2349                    }
2350                }
2351            }
2352        }
2353
2354        for (pane, item) in items_to_activate {
2355            let pane_was_focused = pane.read(cx).has_focus();
2356            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2357                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2358            } else {
2359                Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2360            }
2361
2362            if pane_was_focused {
2363                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2364            }
2365        }
2366
2367        None
2368    }
2369
2370    fn shared_screen_for_peer(
2371        &self,
2372        peer_id: PeerId,
2373        pane: &ViewHandle<Pane>,
2374        cx: &mut ViewContext<Self>,
2375    ) -> Option<ViewHandle<SharedScreen>> {
2376        let call = self.active_call()?;
2377        let room = call.read(cx).room()?.read(cx);
2378        let participant = room.remote_participant_for_peer_id(peer_id)?;
2379        let track = participant.tracks.values().next()?.clone();
2380        let user = participant.user.clone();
2381
2382        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2383            if item.read(cx).peer_id == peer_id {
2384                return Some(item);
2385            }
2386        }
2387
2388        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2389    }
2390
2391    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2392        if active {
2393            cx.background()
2394                .spawn(persistence::DB.update_timestamp(self.database_id()))
2395                .detach();
2396        } else {
2397            for pane in &self.panes {
2398                pane.update(cx, |pane, cx| {
2399                    if let Some(item) = pane.active_item() {
2400                        item.workspace_deactivated(cx);
2401                    }
2402                    if matches!(
2403                        cx.global::<Settings>().autosave,
2404                        Autosave::OnWindowChange | Autosave::OnFocusChange
2405                    ) {
2406                        for item in pane.items() {
2407                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2408                                .detach_and_log_err(cx);
2409                        }
2410                    }
2411                });
2412            }
2413        }
2414    }
2415
2416    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2417        self.active_call.as_ref().map(|(call, _)| call)
2418    }
2419
2420    fn on_active_call_event(
2421        &mut self,
2422        _: ModelHandle<ActiveCall>,
2423        event: &call::room::Event,
2424        cx: &mut ViewContext<Self>,
2425    ) {
2426        match event {
2427            call::room::Event::ParticipantLocationChanged { participant_id }
2428            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2429                self.leader_updated(*participant_id, cx);
2430            }
2431            _ => {}
2432        }
2433    }
2434
2435    pub fn database_id(&self) -> WorkspaceId {
2436        self.database_id
2437    }
2438
2439    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2440        let project = self.project().read(cx);
2441
2442        if project.is_local() {
2443            Some(
2444                project
2445                    .visible_worktrees(cx)
2446                    .map(|worktree| worktree.read(cx).abs_path())
2447                    .collect::<Vec<_>>()
2448                    .into(),
2449            )
2450        } else {
2451            None
2452        }
2453    }
2454
2455    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2456        match member {
2457            Member::Axis(PaneAxis { members, .. }) => {
2458                for child in members.iter() {
2459                    self.remove_panes(child.clone(), cx)
2460                }
2461            }
2462            Member::Pane(pane) => self.remove_pane(pane.clone(), cx),
2463        }
2464    }
2465
2466    fn serialize_workspace(&self, cx: &AppContext) {
2467        fn serialize_pane_handle(
2468            pane_handle: &ViewHandle<Pane>,
2469            cx: &AppContext,
2470        ) -> SerializedPane {
2471            let (items, active) = {
2472                let pane = pane_handle.read(cx);
2473                let active_item_id = pane.active_item().map(|item| item.id());
2474                (
2475                    pane.items()
2476                        .filter_map(|item_handle| {
2477                            Some(SerializedItem {
2478                                kind: Arc::from(item_handle.serialized_item_kind()?),
2479                                item_id: item_handle.id(),
2480                                active: Some(item_handle.id()) == active_item_id,
2481                            })
2482                        })
2483                        .collect::<Vec<_>>(),
2484                    pane.is_active(),
2485                )
2486            };
2487
2488            SerializedPane::new(items, active)
2489        }
2490
2491        fn build_serialized_pane_group(
2492            pane_group: &Member,
2493            cx: &AppContext,
2494        ) -> SerializedPaneGroup {
2495            match pane_group {
2496                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2497                    axis: *axis,
2498                    children: members
2499                        .iter()
2500                        .map(|member| build_serialized_pane_group(member, cx))
2501                        .collect::<Vec<_>>(),
2502                },
2503                Member::Pane(pane_handle) => {
2504                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2505                }
2506            }
2507        }
2508
2509        if let Some(location) = self.location(cx) {
2510            // Load bearing special case:
2511            //  - with_local_workspace() relies on this to not have other stuff open
2512            //    when you open your log
2513            if !location.paths().is_empty() {
2514                let dock_pane = serialize_pane_handle(self.dock.pane(), cx);
2515                let center_group = build_serialized_pane_group(&self.center.root, cx);
2516
2517                let serialized_workspace = SerializedWorkspace {
2518                    id: self.database_id,
2519                    location,
2520                    dock_position: self.dock.position(),
2521                    dock_pane,
2522                    center_group,
2523                    left_sidebar_open: self.left_sidebar.read(cx).is_open(),
2524                    bounds: Default::default(),
2525                    display: Default::default(),
2526                };
2527
2528                cx.background()
2529                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2530                    .detach();
2531            }
2532        }
2533    }
2534
2535    fn load_from_serialized_workspace(
2536        workspace: WeakViewHandle<Workspace>,
2537        serialized_workspace: SerializedWorkspace,
2538        cx: &mut AppContext,
2539    ) {
2540        cx.spawn(|mut cx| async move {
2541            let (project, dock_pane_handle, old_center_pane) =
2542                workspace.read_with(&cx, |workspace, _| {
2543                    (
2544                        workspace.project().clone(),
2545                        workspace.dock_pane().downgrade(),
2546                        workspace.last_active_center_pane.clone(),
2547                    )
2548                })?;
2549
2550            serialized_workspace
2551                .dock_pane
2552                .deserialize_to(
2553                    &project,
2554                    &dock_pane_handle,
2555                    serialized_workspace.id,
2556                    &workspace,
2557                    &mut cx,
2558                )
2559                .await?;
2560
2561            // Traverse the splits tree and add to things
2562            let center_group = serialized_workspace
2563                .center_group
2564                .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2565                .await;
2566
2567            // Remove old panes from workspace panes list
2568            workspace.update(&mut cx, |workspace, cx| {
2569                if let Some((center_group, active_pane)) = center_group {
2570                    workspace.remove_panes(workspace.center.root.clone(), cx);
2571
2572                    // Swap workspace center group
2573                    workspace.center = PaneGroup::with_root(center_group);
2574
2575                    // Change the focus to the workspace first so that we retrigger focus in on the pane.
2576                    cx.focus_self();
2577
2578                    if let Some(active_pane) = active_pane {
2579                        cx.focus(&active_pane);
2580                    } else {
2581                        cx.focus(workspace.panes.last().unwrap());
2582                    }
2583                } else {
2584                    let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
2585                    if let Some(old_center_handle) = old_center_handle {
2586                        cx.focus(&old_center_handle)
2587                    } else {
2588                        cx.focus_self()
2589                    }
2590                }
2591
2592                if workspace.left_sidebar().read(cx).is_open()
2593                    != serialized_workspace.left_sidebar_open
2594                {
2595                    workspace.toggle_sidebar(SidebarSide::Left, cx);
2596                }
2597
2598                // Note that without after_window, the focus_self() and
2599                // the focus the dock generates start generating alternating
2600                // focus due to the deferred execution each triggering each other
2601                cx.after_window_update(move |workspace, cx| {
2602                    Dock::set_dock_position(
2603                        workspace,
2604                        serialized_workspace.dock_position,
2605                        true,
2606                        cx,
2607                    );
2608                });
2609
2610                cx.notify();
2611            })?;
2612
2613            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
2614            workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
2615            anyhow::Ok(())
2616        })
2617        .detach_and_log_err(cx);
2618    }
2619
2620    #[cfg(any(test, feature = "test-support"))]
2621    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
2622        let app_state = Arc::new(AppState {
2623            languages: project.read(cx).languages().clone(),
2624            themes: ThemeRegistry::new((), cx.font_cache().clone()),
2625            client: project.read(cx).client(),
2626            user_store: project.read(cx).user_store(),
2627            fs: project.read(cx).fs().clone(),
2628            build_window_options: |_, _, _| Default::default(),
2629            initialize_workspace: |_, _, _| {},
2630            dock_default_item_factory: |_, _| None,
2631            background_actions: || &[],
2632        });
2633        Self::new(None, 0, project, app_state, cx)
2634    }
2635}
2636
2637fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
2638    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
2639
2640    workspace
2641        .update(cx, |workspace, cx| {
2642            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
2643                workspace.show_notification_once(0, cx, |cx| {
2644                    cx.add_view(|_| {
2645                        MessageNotification::new("Failed to load any database file.")
2646                            .with_click_message("Click to let us know about this error")
2647                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
2648                    })
2649                });
2650            } else {
2651                let backup_path = (*db::BACKUP_DB_PATH).read();
2652                if let Some(backup_path) = backup_path.clone() {
2653                    workspace.show_notification_once(0, cx, move |cx| {
2654                        cx.add_view(move |_| {
2655                            MessageNotification::new(format!(
2656                                "Database file was corrupted. Old database backed up to {}",
2657                                backup_path.display()
2658                            ))
2659                            .with_click_message("Click to show old database in finder")
2660                            .on_click(move |cx| {
2661                                cx.platform().open_url(&backup_path.to_string_lossy())
2662                            })
2663                        })
2664                    });
2665                }
2666            }
2667        })
2668        .log_err();
2669}
2670
2671impl Entity for Workspace {
2672    type Event = Event;
2673}
2674
2675impl View for Workspace {
2676    fn ui_name() -> &'static str {
2677        "Workspace"
2678    }
2679
2680    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2681        let theme = cx.global::<Settings>().theme.clone();
2682        Stack::new()
2683            .with_child(
2684                Flex::column()
2685                    .with_child(self.render_titlebar(&theme, cx))
2686                    .with_child(
2687                        Stack::new()
2688                            .with_child({
2689                                let project = self.project.clone();
2690                                Flex::row()
2691                                    .with_children(
2692                                        if self.left_sidebar.read(cx).active_item().is_some() {
2693                                            Some(
2694                                                ChildView::new(&self.left_sidebar, cx)
2695                                                    .constrained()
2696                                                    .dynamically(|constraint, _, cx| {
2697                                                        SizeConstraint::new(
2698                                                            Vector2F::new(20., constraint.min.y()),
2699                                                            Vector2F::new(
2700                                                                cx.window_size().x() * 0.8,
2701                                                                constraint.max.y(),
2702                                                            ),
2703                                                        )
2704                                                    }),
2705                                            )
2706                                        } else {
2707                                            None
2708                                        },
2709                                    )
2710                                    .with_child(
2711                                        FlexItem::new(
2712                                            Flex::column()
2713                                                .with_child(
2714                                                    FlexItem::new(self.center.render(
2715                                                        &project,
2716                                                        &theme,
2717                                                        &self.follower_states_by_leader,
2718                                                        self.active_call(),
2719                                                        self.active_pane(),
2720                                                        &self.app_state,
2721                                                        cx,
2722                                                    ))
2723                                                    .flex(1., true),
2724                                                )
2725                                                .with_children(self.dock.render(
2726                                                    &theme,
2727                                                    DockAnchor::Bottom,
2728                                                    cx,
2729                                                )),
2730                                        )
2731                                        .flex(1., true),
2732                                    )
2733                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2734                                    .with_children(
2735                                        if self.right_sidebar.read(cx).active_item().is_some() {
2736                                            Some(
2737                                                ChildView::new(&self.right_sidebar, cx)
2738                                                    .constrained()
2739                                                    .dynamically(|constraint, _, cx| {
2740                                                        SizeConstraint::new(
2741                                                            Vector2F::new(20., constraint.min.y()),
2742                                                            Vector2F::new(
2743                                                                cx.window_size().x() * 0.8,
2744                                                                constraint.max.y(),
2745                                                            ),
2746                                                        )
2747                                                    }),
2748                                            )
2749                                        } else {
2750                                            None
2751                                        },
2752                                    )
2753                            })
2754                            .with_child(Overlay::new(
2755                                Stack::new()
2756                                    .with_children(self.dock.render(
2757                                        &theme,
2758                                        DockAnchor::Expanded,
2759                                        cx,
2760                                    ))
2761                                    .with_children(self.modal.as_ref().map(|modal| {
2762                                        ChildView::new(modal, cx)
2763                                            .contained()
2764                                            .with_style(theme.workspace.modal)
2765                                            .aligned()
2766                                            .top()
2767                                    }))
2768                                    .with_children(self.render_notifications(&theme.workspace, cx)),
2769                            ))
2770                            .flex(1.0, true),
2771                    )
2772                    .with_child(ChildView::new(&self.status_bar, cx))
2773                    .contained()
2774                    .with_background_color(theme.workspace.background),
2775            )
2776            .with_children(DragAndDrop::render(cx))
2777            .with_children(self.render_disconnected_overlay(cx))
2778            .into_any_named("workspace")
2779    }
2780
2781    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
2782        if cx.is_self_focused() {
2783            cx.focus(&self.active_pane);
2784        }
2785    }
2786}
2787
2788impl ViewId {
2789    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
2790        Ok(Self {
2791            creator: message
2792                .creator
2793                .ok_or_else(|| anyhow!("creator is missing"))?,
2794            id: message.id,
2795        })
2796    }
2797
2798    pub(crate) fn to_proto(&self) -> proto::ViewId {
2799        proto::ViewId {
2800            creator: Some(self.creator),
2801            id: self.id,
2802        }
2803    }
2804}
2805
2806pub trait WorkspaceHandle {
2807    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2808}
2809
2810impl WorkspaceHandle for ViewHandle<Workspace> {
2811    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2812        self.read(cx)
2813            .worktrees(cx)
2814            .flat_map(|worktree| {
2815                let worktree_id = worktree.read(cx).id();
2816                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2817                    worktree_id,
2818                    path: f.path.clone(),
2819                })
2820            })
2821            .collect::<Vec<_>>()
2822    }
2823}
2824
2825impl std::fmt::Debug for OpenPaths {
2826    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2827        f.debug_struct("OpenPaths")
2828            .field("paths", &self.paths)
2829            .finish()
2830    }
2831}
2832
2833pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2834
2835pub fn activate_workspace_for_project(
2836    cx: &mut AsyncAppContext,
2837    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2838) -> Option<WeakViewHandle<Workspace>> {
2839    for window_id in cx.window_ids() {
2840        let handle = cx
2841            .update_window(window_id, |cx| {
2842                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
2843                    let project = workspace_handle.read(cx).project.clone();
2844                    if project.update(cx, &predicate) {
2845                        cx.activate_window();
2846                        return Some(workspace_handle.clone());
2847                    }
2848                }
2849                None
2850            })
2851            .flatten();
2852
2853        if let Some(handle) = handle {
2854            return Some(handle.downgrade());
2855        }
2856    }
2857    None
2858}
2859
2860pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
2861    DB.last_workspace().await.log_err().flatten()
2862}
2863
2864#[allow(clippy::type_complexity)]
2865pub fn open_paths(
2866    abs_paths: &[PathBuf],
2867    app_state: &Arc<AppState>,
2868    requesting_window_id: Option<usize>,
2869    cx: &mut AppContext,
2870) -> Task<
2871    Result<(
2872        WeakViewHandle<Workspace>,
2873        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
2874    )>,
2875> {
2876    log::info!("open paths {:?}", abs_paths);
2877
2878    let app_state = app_state.clone();
2879    let abs_paths = abs_paths.to_vec();
2880    cx.spawn(|mut cx| async move {
2881        // Open paths in existing workspace if possible
2882        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
2883            project.contains_paths(&abs_paths, cx)
2884        });
2885
2886        if let Some(existing) = existing {
2887            Ok((
2888                existing.clone(),
2889                existing
2890                    .update(&mut cx, |workspace, cx| {
2891                        workspace.open_paths(abs_paths, true, cx)
2892                    })?
2893                    .await,
2894            ))
2895        } else {
2896            let contains_directory =
2897                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2898                    .await
2899                    .contains(&false);
2900
2901            cx.update(|cx| {
2902                let task =
2903                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx);
2904
2905                cx.spawn(|mut cx| async move {
2906                    let (workspace, items) = task.await;
2907
2908                    workspace.update(&mut cx, |workspace, cx| {
2909                        if contains_directory {
2910                            workspace.toggle_sidebar(SidebarSide::Left, cx);
2911                        }
2912                    })?;
2913
2914                    anyhow::Ok((workspace, items))
2915                })
2916            })
2917            .await
2918        }
2919    })
2920}
2921
2922pub fn open_new(
2923    app_state: &Arc<AppState>,
2924    cx: &mut AppContext,
2925    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
2926) -> Task<()> {
2927    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
2928    cx.spawn(|mut cx| async move {
2929        let (workspace, opened_paths) = task.await;
2930
2931        workspace
2932            .update(&mut cx, |workspace, cx| {
2933                if opened_paths.is_empty() {
2934                    init(workspace, cx)
2935                }
2936            })
2937            .log_err();
2938    })
2939}
2940
2941pub fn create_and_open_local_file(
2942    path: &'static Path,
2943    cx: &mut ViewContext<Workspace>,
2944    default_content: impl 'static + Send + FnOnce() -> Rope,
2945) -> Task<Result<Box<dyn ItemHandle>>> {
2946    cx.spawn(|workspace, mut cx| async move {
2947        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
2948        if !fs.is_file(path).await {
2949            fs.create_file(path, Default::default()).await?;
2950            fs.save(path, &default_content(), Default::default())
2951                .await?;
2952        }
2953
2954        let mut items = workspace
2955            .update(&mut cx, |workspace, cx| {
2956                workspace.with_local_workspace(cx, |workspace, cx| {
2957                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
2958                })
2959            })?
2960            .await?
2961            .await;
2962
2963        let item = items.pop().flatten();
2964        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
2965    })
2966}
2967
2968pub fn join_remote_project(
2969    project_id: u64,
2970    follow_user_id: u64,
2971    app_state: Arc<AppState>,
2972    cx: &mut AppContext,
2973) -> Task<Result<()>> {
2974    cx.spawn(|mut cx| async move {
2975        let existing_workspace = cx
2976            .window_ids()
2977            .into_iter()
2978            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
2979            .find(|workspace| {
2980                cx.read_window(workspace.window_id(), |cx| {
2981                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
2982                })
2983                .unwrap_or(false)
2984            });
2985
2986        let workspace = if let Some(existing_workspace) = existing_workspace {
2987            existing_workspace.downgrade()
2988        } else {
2989            let active_call = cx.read(ActiveCall::global);
2990            let room = active_call
2991                .read_with(&cx, |call, _| call.room().cloned())
2992                .ok_or_else(|| anyhow!("not in a call"))?;
2993            let project = room
2994                .update(&mut cx, |room, cx| {
2995                    room.join_project(
2996                        project_id,
2997                        app_state.languages.clone(),
2998                        app_state.fs.clone(),
2999                        cx,
3000                    )
3001                })
3002                .await?;
3003
3004            let (_, workspace) = cx.add_window(
3005                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
3006                |cx| {
3007                    let mut workspace =
3008                        Workspace::new(Default::default(), 0, project, app_state.clone(), cx);
3009                    (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
3010                    workspace
3011                },
3012            );
3013            workspace.downgrade()
3014        };
3015
3016        cx.activate_window(workspace.window_id());
3017        cx.platform().activate(true);
3018
3019        workspace.update(&mut cx, |workspace, cx| {
3020            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3021                let follow_peer_id = room
3022                    .read(cx)
3023                    .remote_participants()
3024                    .iter()
3025                    .find(|(_, participant)| participant.user.id == follow_user_id)
3026                    .map(|(_, p)| p.peer_id)
3027                    .or_else(|| {
3028                        // If we couldn't follow the given user, follow the host instead.
3029                        let collaborator = workspace
3030                            .project()
3031                            .read(cx)
3032                            .collaborators()
3033                            .values()
3034                            .find(|collaborator| collaborator.replica_id == 0)?;
3035                        Some(collaborator.peer_id)
3036                    });
3037
3038                if let Some(follow_peer_id) = follow_peer_id {
3039                    if !workspace.is_being_followed(follow_peer_id) {
3040                        workspace
3041                            .toggle_follow(follow_peer_id, cx)
3042                            .map(|follow| follow.detach_and_log_err(cx));
3043                    }
3044                }
3045            }
3046        })?;
3047
3048        anyhow::Ok(())
3049    })
3050}
3051
3052pub fn restart(_: &Restart, cx: &mut AppContext) {
3053    let should_confirm = cx.global::<Settings>().confirm_quit;
3054    cx.spawn(|mut cx| async move {
3055        let mut workspaces = cx
3056            .window_ids()
3057            .into_iter()
3058            .filter_map(|window_id| {
3059                Some(
3060                    cx.root_view(window_id)?
3061                        .clone()
3062                        .downcast::<Workspace>()?
3063                        .downgrade(),
3064                )
3065            })
3066            .collect::<Vec<_>>();
3067
3068        // If multiple windows have unsaved changes, and need a save prompt,
3069        // prompt in the active window before switching to a different window.
3070        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3071
3072        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3073            let answer = cx.prompt(
3074                workspace.window_id(),
3075                PromptLevel::Info,
3076                "Are you sure you want to restart?",
3077                &["Restart", "Cancel"],
3078            );
3079
3080            if let Some(mut answer) = answer {
3081                let answer = answer.next().await;
3082                if answer != Some(0) {
3083                    return Ok(());
3084                }
3085            }
3086        }
3087
3088        // If the user cancels any save prompt, then keep the app open.
3089        for workspace in workspaces {
3090            if !workspace
3091                .update(&mut cx, |workspace, cx| {
3092                    workspace.prepare_to_close(true, cx)
3093                })?
3094                .await?
3095            {
3096                return Ok(());
3097            }
3098        }
3099        cx.platform().restart();
3100        anyhow::Ok(())
3101    })
3102    .detach_and_log_err(cx);
3103}
3104
3105fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3106    let mut parts = value.split(',');
3107    let width: usize = parts.next()?.parse().ok()?;
3108    let height: usize = parts.next()?.parse().ok()?;
3109    Some(vec2f(width as f32, height as f32))
3110}
3111
3112#[cfg(test)]
3113mod tests {
3114    use std::{cell::RefCell, rc::Rc};
3115
3116    use crate::item::test::{TestItem, TestItemEvent, TestProjectItem};
3117
3118    use super::*;
3119    use fs::FakeFs;
3120    use gpui::{executor::Deterministic, TestAppContext};
3121    use project::{Project, ProjectEntryId};
3122    use serde_json::json;
3123
3124    #[gpui::test]
3125    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3126        cx.foreground().forbid_parking();
3127        Settings::test_async(cx);
3128
3129        let fs = FakeFs::new(cx.background());
3130        let project = Project::test(fs, [], cx).await;
3131        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3132
3133        // Adding an item with no ambiguity renders the tab without detail.
3134        let item1 = cx.add_view(window_id, |_| {
3135            let mut item = TestItem::new();
3136            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3137            item
3138        });
3139        workspace.update(cx, |workspace, cx| {
3140            workspace.add_item(Box::new(item1.clone()), cx);
3141        });
3142        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3143
3144        // Adding an item that creates ambiguity increases the level of detail on
3145        // both tabs.
3146        let item2 = cx.add_view(window_id, |_| {
3147            let mut item = TestItem::new();
3148            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3149            item
3150        });
3151        workspace.update(cx, |workspace, cx| {
3152            workspace.add_item(Box::new(item2.clone()), cx);
3153        });
3154        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3155        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3156
3157        // Adding an item that creates ambiguity increases the level of detail only
3158        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3159        // we stop at the highest detail available.
3160        let item3 = cx.add_view(window_id, |_| {
3161            let mut item = TestItem::new();
3162            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3163            item
3164        });
3165        workspace.update(cx, |workspace, cx| {
3166            workspace.add_item(Box::new(item3.clone()), cx);
3167        });
3168        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3169        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3170        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3171    }
3172
3173    #[gpui::test]
3174    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3175        cx.foreground().forbid_parking();
3176        Settings::test_async(cx);
3177        let fs = FakeFs::new(cx.background());
3178        fs.insert_tree(
3179            "/root1",
3180            json!({
3181                "one.txt": "",
3182                "two.txt": "",
3183            }),
3184        )
3185        .await;
3186        fs.insert_tree(
3187            "/root2",
3188            json!({
3189                "three.txt": "",
3190            }),
3191        )
3192        .await;
3193
3194        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3195        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3196        let worktree_id = project.read_with(cx, |project, cx| {
3197            project.worktrees(cx).next().unwrap().read(cx).id()
3198        });
3199
3200        let item1 = cx.add_view(window_id, |cx| {
3201            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3202        });
3203        let item2 = cx.add_view(window_id, |cx| {
3204            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3205        });
3206
3207        // Add an item to an empty pane
3208        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3209        project.read_with(cx, |project, cx| {
3210            assert_eq!(
3211                project.active_entry(),
3212                project
3213                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3214                    .map(|e| e.id)
3215            );
3216        });
3217        assert_eq!(
3218            cx.current_window_title(window_id).as_deref(),
3219            Some("one.txt — root1")
3220        );
3221
3222        // Add a second item to a non-empty pane
3223        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3224        assert_eq!(
3225            cx.current_window_title(window_id).as_deref(),
3226            Some("two.txt — root1")
3227        );
3228        project.read_with(cx, |project, cx| {
3229            assert_eq!(
3230                project.active_entry(),
3231                project
3232                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3233                    .map(|e| e.id)
3234            );
3235        });
3236
3237        // Close the active item
3238        workspace
3239            .update(cx, |workspace, cx| {
3240                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
3241            })
3242            .await
3243            .unwrap();
3244        assert_eq!(
3245            cx.current_window_title(window_id).as_deref(),
3246            Some("one.txt — root1")
3247        );
3248        project.read_with(cx, |project, cx| {
3249            assert_eq!(
3250                project.active_entry(),
3251                project
3252                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3253                    .map(|e| e.id)
3254            );
3255        });
3256
3257        // Add a project folder
3258        project
3259            .update(cx, |project, cx| {
3260                project.find_or_create_local_worktree("/root2", true, cx)
3261            })
3262            .await
3263            .unwrap();
3264        assert_eq!(
3265            cx.current_window_title(window_id).as_deref(),
3266            Some("one.txt — root1, root2")
3267        );
3268
3269        // Remove a project folder
3270        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3271        assert_eq!(
3272            cx.current_window_title(window_id).as_deref(),
3273            Some("one.txt — root2")
3274        );
3275    }
3276
3277    #[gpui::test]
3278    async fn test_close_window(cx: &mut TestAppContext) {
3279        cx.foreground().forbid_parking();
3280        Settings::test_async(cx);
3281        let fs = FakeFs::new(cx.background());
3282        fs.insert_tree("/root", json!({ "one": "" })).await;
3283
3284        let project = Project::test(fs, ["root".as_ref()], cx).await;
3285        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3286
3287        // When there are no dirty items, there's nothing to do.
3288        let item1 = cx.add_view(window_id, |_| TestItem::new());
3289        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3290        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3291        assert!(task.await.unwrap());
3292
3293        // When there are dirty untitled items, prompt to save each one. If the user
3294        // cancels any prompt, then abort.
3295        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
3296        let item3 = cx.add_view(window_id, |cx| {
3297            TestItem::new()
3298                .with_dirty(true)
3299                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3300        });
3301        workspace.update(cx, |w, cx| {
3302            w.add_item(Box::new(item2.clone()), cx);
3303            w.add_item(Box::new(item3.clone()), cx);
3304        });
3305        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3306        cx.foreground().run_until_parked();
3307        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3308        cx.foreground().run_until_parked();
3309        assert!(!cx.has_pending_prompt(window_id));
3310        assert!(!task.await.unwrap());
3311    }
3312
3313    #[gpui::test]
3314    async fn test_close_pane_items(cx: &mut TestAppContext) {
3315        cx.foreground().forbid_parking();
3316        Settings::test_async(cx);
3317        let fs = FakeFs::new(cx.background());
3318
3319        let project = Project::test(fs, None, cx).await;
3320        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3321
3322        let item1 = cx.add_view(window_id, |cx| {
3323            TestItem::new()
3324                .with_dirty(true)
3325                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3326        });
3327        let item2 = cx.add_view(window_id, |cx| {
3328            TestItem::new()
3329                .with_dirty(true)
3330                .with_conflict(true)
3331                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3332        });
3333        let item3 = cx.add_view(window_id, |cx| {
3334            TestItem::new()
3335                .with_dirty(true)
3336                .with_conflict(true)
3337                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3338        });
3339        let item4 = cx.add_view(window_id, |cx| {
3340            TestItem::new()
3341                .with_dirty(true)
3342                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3343        });
3344        let pane = workspace.update(cx, |workspace, cx| {
3345            workspace.add_item(Box::new(item1.clone()), cx);
3346            workspace.add_item(Box::new(item2.clone()), cx);
3347            workspace.add_item(Box::new(item3.clone()), cx);
3348            workspace.add_item(Box::new(item4.clone()), cx);
3349            workspace.active_pane().clone()
3350        });
3351
3352        let close_items = workspace.update(cx, |workspace, cx| {
3353            pane.update(cx, |pane, cx| {
3354                pane.activate_item(1, true, true, cx);
3355                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3356            });
3357
3358            let item1_id = item1.id();
3359            let item3_id = item3.id();
3360            let item4_id = item4.id();
3361            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3362                [item1_id, item3_id, item4_id].contains(&id)
3363            })
3364        });
3365        cx.foreground().run_until_parked();
3366
3367        // There's a prompt to save item 1.
3368        pane.read_with(cx, |pane, _| {
3369            assert_eq!(pane.items_len(), 4);
3370            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3371        });
3372        assert!(cx.has_pending_prompt(window_id));
3373
3374        // Confirm saving item 1.
3375        cx.simulate_prompt_answer(window_id, 0);
3376        cx.foreground().run_until_parked();
3377
3378        // Item 1 is saved. There's a prompt to save item 3.
3379        pane.read_with(cx, |pane, cx| {
3380            assert_eq!(item1.read(cx).save_count, 1);
3381            assert_eq!(item1.read(cx).save_as_count, 0);
3382            assert_eq!(item1.read(cx).reload_count, 0);
3383            assert_eq!(pane.items_len(), 3);
3384            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3385        });
3386        assert!(cx.has_pending_prompt(window_id));
3387
3388        // Cancel saving item 3.
3389        cx.simulate_prompt_answer(window_id, 1);
3390        cx.foreground().run_until_parked();
3391
3392        // Item 3 is reloaded. There's a prompt to save item 4.
3393        pane.read_with(cx, |pane, cx| {
3394            assert_eq!(item3.read(cx).save_count, 0);
3395            assert_eq!(item3.read(cx).save_as_count, 0);
3396            assert_eq!(item3.read(cx).reload_count, 1);
3397            assert_eq!(pane.items_len(), 2);
3398            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3399        });
3400        assert!(cx.has_pending_prompt(window_id));
3401
3402        // Confirm saving item 4.
3403        cx.simulate_prompt_answer(window_id, 0);
3404        cx.foreground().run_until_parked();
3405
3406        // There's a prompt for a path for item 4.
3407        cx.simulate_new_path_selection(|_| Some(Default::default()));
3408        close_items.await.unwrap();
3409
3410        // The requested items are closed.
3411        pane.read_with(cx, |pane, cx| {
3412            assert_eq!(item4.read(cx).save_count, 0);
3413            assert_eq!(item4.read(cx).save_as_count, 1);
3414            assert_eq!(item4.read(cx).reload_count, 0);
3415            assert_eq!(pane.items_len(), 1);
3416            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3417        });
3418    }
3419
3420    #[gpui::test]
3421    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3422        cx.foreground().forbid_parking();
3423        Settings::test_async(cx);
3424        let fs = FakeFs::new(cx.background());
3425
3426        let project = Project::test(fs, [], cx).await;
3427        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3428
3429        // Create several workspace items with single project entries, and two
3430        // workspace items with multiple project entries.
3431        let single_entry_items = (0..=4)
3432            .map(|project_entry_id| {
3433                cx.add_view(window_id, |cx| {
3434                    TestItem::new()
3435                        .with_dirty(true)
3436                        .with_project_items(&[TestProjectItem::new(
3437                            project_entry_id,
3438                            &format!("{project_entry_id}.txt"),
3439                            cx,
3440                        )])
3441                })
3442            })
3443            .collect::<Vec<_>>();
3444        let item_2_3 = cx.add_view(window_id, |cx| {
3445            TestItem::new()
3446                .with_dirty(true)
3447                .with_singleton(false)
3448                .with_project_items(&[
3449                    single_entry_items[2].read(cx).project_items[0].clone(),
3450                    single_entry_items[3].read(cx).project_items[0].clone(),
3451                ])
3452        });
3453        let item_3_4 = cx.add_view(window_id, |cx| {
3454            TestItem::new()
3455                .with_dirty(true)
3456                .with_singleton(false)
3457                .with_project_items(&[
3458                    single_entry_items[3].read(cx).project_items[0].clone(),
3459                    single_entry_items[4].read(cx).project_items[0].clone(),
3460                ])
3461        });
3462
3463        // Create two panes that contain the following project entries:
3464        //   left pane:
3465        //     multi-entry items:   (2, 3)
3466        //     single-entry items:  0, 1, 2, 3, 4
3467        //   right pane:
3468        //     single-entry items:  1
3469        //     multi-entry items:   (3, 4)
3470        let left_pane = workspace.update(cx, |workspace, cx| {
3471            let left_pane = workspace.active_pane().clone();
3472            workspace.add_item(Box::new(item_2_3.clone()), cx);
3473            for item in single_entry_items {
3474                workspace.add_item(Box::new(item), cx);
3475            }
3476            left_pane.update(cx, |pane, cx| {
3477                pane.activate_item(2, true, true, cx);
3478            });
3479
3480            workspace
3481                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3482                .unwrap();
3483
3484            left_pane
3485        });
3486
3487        //Need to cause an effect flush in order to respect new focus
3488        workspace.update(cx, |workspace, cx| {
3489            workspace.add_item(Box::new(item_3_4.clone()), cx);
3490            cx.focus(&left_pane);
3491        });
3492
3493        // When closing all of the items in the left pane, we should be prompted twice:
3494        // once for project entry 0, and once for project entry 2. After those two
3495        // prompts, the task should complete.
3496
3497        let close = workspace.update(cx, |workspace, cx| {
3498            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3499        });
3500
3501        cx.foreground().run_until_parked();
3502        left_pane.read_with(cx, |pane, cx| {
3503            assert_eq!(
3504                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3505                &[ProjectEntryId::from_proto(0)]
3506            );
3507        });
3508        cx.simulate_prompt_answer(window_id, 0);
3509
3510        cx.foreground().run_until_parked();
3511        left_pane.read_with(cx, |pane, cx| {
3512            assert_eq!(
3513                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3514                &[ProjectEntryId::from_proto(2)]
3515            );
3516        });
3517        cx.simulate_prompt_answer(window_id, 0);
3518
3519        cx.foreground().run_until_parked();
3520        close.await.unwrap();
3521        left_pane.read_with(cx, |pane, _| {
3522            assert_eq!(pane.items_len(), 0);
3523        });
3524    }
3525
3526    #[gpui::test]
3527    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3528        deterministic.forbid_parking();
3529
3530        Settings::test_async(cx);
3531        let fs = FakeFs::new(cx.background());
3532
3533        let project = Project::test(fs, [], cx).await;
3534        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3535
3536        let item = cx.add_view(window_id, |cx| {
3537            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3538        });
3539        let item_id = item.id();
3540        workspace.update(cx, |workspace, cx| {
3541            workspace.add_item(Box::new(item.clone()), cx);
3542        });
3543
3544        // Autosave on window change.
3545        item.update(cx, |item, cx| {
3546            cx.update_global(|settings: &mut Settings, _| {
3547                settings.autosave = Autosave::OnWindowChange;
3548            });
3549            item.is_dirty = true;
3550        });
3551
3552        // Deactivating the window saves the file.
3553        cx.simulate_window_activation(None);
3554        deterministic.run_until_parked();
3555        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3556
3557        // Autosave on focus change.
3558        item.update(cx, |item, cx| {
3559            cx.focus_self();
3560            cx.update_global(|settings: &mut Settings, _| {
3561                settings.autosave = Autosave::OnFocusChange;
3562            });
3563            item.is_dirty = true;
3564        });
3565
3566        // Blurring the item saves the file.
3567        item.update(cx, |_, cx| cx.blur());
3568        deterministic.run_until_parked();
3569        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3570
3571        // Deactivating the window still saves the file.
3572        cx.simulate_window_activation(Some(window_id));
3573        item.update(cx, |item, cx| {
3574            cx.focus_self();
3575            item.is_dirty = true;
3576        });
3577        cx.simulate_window_activation(None);
3578
3579        deterministic.run_until_parked();
3580        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3581
3582        // Autosave after delay.
3583        item.update(cx, |item, cx| {
3584            cx.update_global(|settings: &mut Settings, _| {
3585                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3586            });
3587            item.is_dirty = true;
3588            cx.emit(TestItemEvent::Edit);
3589        });
3590
3591        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3592        deterministic.advance_clock(Duration::from_millis(250));
3593        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3594
3595        // After delay expires, the file is saved.
3596        deterministic.advance_clock(Duration::from_millis(250));
3597        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3598
3599        // Autosave on focus change, ensuring closing the tab counts as such.
3600        item.update(cx, |item, cx| {
3601            cx.update_global(|settings: &mut Settings, _| {
3602                settings.autosave = Autosave::OnFocusChange;
3603            });
3604            item.is_dirty = true;
3605        });
3606
3607        workspace
3608            .update(cx, |workspace, cx| {
3609                let pane = workspace.active_pane().clone();
3610                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3611            })
3612            .await
3613            .unwrap();
3614        assert!(!cx.has_pending_prompt(window_id));
3615        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3616
3617        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3618        workspace.update(cx, |workspace, cx| {
3619            workspace.add_item(Box::new(item.clone()), cx);
3620        });
3621        item.update(cx, |item, cx| {
3622            item.project_items[0].update(cx, |item, _| {
3623                item.entry_id = None;
3624            });
3625            item.is_dirty = true;
3626            cx.blur();
3627        });
3628        deterministic.run_until_parked();
3629        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3630
3631        // Ensure autosave is prevented for deleted files also when closing the buffer.
3632        let _close_items = workspace.update(cx, |workspace, cx| {
3633            let pane = workspace.active_pane().clone();
3634            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3635        });
3636        deterministic.run_until_parked();
3637        assert!(cx.has_pending_prompt(window_id));
3638        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3639    }
3640
3641    #[gpui::test]
3642    async fn test_pane_navigation(
3643        deterministic: Arc<Deterministic>,
3644        cx: &mut gpui::TestAppContext,
3645    ) {
3646        deterministic.forbid_parking();
3647        Settings::test_async(cx);
3648        let fs = FakeFs::new(cx.background());
3649
3650        let project = Project::test(fs, [], cx).await;
3651        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3652
3653        let item = cx.add_view(window_id, |cx| {
3654            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3655        });
3656        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3657        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3658        let toolbar_notify_count = Rc::new(RefCell::new(0));
3659
3660        workspace.update(cx, |workspace, cx| {
3661            workspace.add_item(Box::new(item.clone()), cx);
3662            let toolbar_notification_count = toolbar_notify_count.clone();
3663            cx.observe(&toolbar, move |_, _, _| {
3664                *toolbar_notification_count.borrow_mut() += 1
3665            })
3666            .detach();
3667        });
3668
3669        pane.read_with(cx, |pane, _| {
3670            assert!(!pane.can_navigate_backward());
3671            assert!(!pane.can_navigate_forward());
3672        });
3673
3674        item.update(cx, |item, cx| {
3675            item.set_state("one".to_string(), cx);
3676        });
3677
3678        // Toolbar must be notified to re-render the navigation buttons
3679        assert_eq!(*toolbar_notify_count.borrow(), 1);
3680
3681        pane.read_with(cx, |pane, _| {
3682            assert!(pane.can_navigate_backward());
3683            assert!(!pane.can_navigate_forward());
3684        });
3685
3686        workspace
3687            .update(cx, |workspace, cx| {
3688                Pane::go_back(workspace, Some(pane.downgrade()), cx)
3689            })
3690            .await
3691            .unwrap();
3692
3693        assert_eq!(*toolbar_notify_count.borrow(), 3);
3694        pane.read_with(cx, |pane, _| {
3695            assert!(!pane.can_navigate_backward());
3696            assert!(pane.can_navigate_forward());
3697        });
3698    }
3699}