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