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