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