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