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 pane_was_focused = pane.read(cx).has_focus();
2343            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2344                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2345            } else {
2346                Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2347            }
2348
2349            if pane_was_focused {
2350                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2351            }
2352        }
2353
2354        None
2355    }
2356
2357    fn shared_screen_for_peer(
2358        &self,
2359        peer_id: PeerId,
2360        pane: &ViewHandle<Pane>,
2361        cx: &mut ViewContext<Self>,
2362    ) -> Option<ViewHandle<SharedScreen>> {
2363        let call = self.active_call()?;
2364        let room = call.read(cx).room()?.read(cx);
2365        let participant = room.remote_participant_for_peer_id(peer_id)?;
2366        let track = participant.tracks.values().next()?.clone();
2367        let user = participant.user.clone();
2368
2369        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2370            if item.read(cx).peer_id == peer_id {
2371                return Some(item);
2372            }
2373        }
2374
2375        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2376    }
2377
2378    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2379        if active {
2380            cx.background()
2381                .spawn(persistence::DB.update_timestamp(self.database_id()))
2382                .detach();
2383        } else {
2384            for pane in &self.panes {
2385                pane.update(cx, |pane, cx| {
2386                    if let Some(item) = pane.active_item() {
2387                        item.workspace_deactivated(cx);
2388                    }
2389                    if matches!(
2390                        cx.global::<Settings>().autosave,
2391                        Autosave::OnWindowChange | Autosave::OnFocusChange
2392                    ) {
2393                        for item in pane.items() {
2394                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2395                                .detach_and_log_err(cx);
2396                        }
2397                    }
2398                });
2399            }
2400        }
2401    }
2402
2403    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2404        self.active_call.as_ref().map(|(call, _)| call)
2405    }
2406
2407    fn on_active_call_event(
2408        &mut self,
2409        _: ModelHandle<ActiveCall>,
2410        event: &call::room::Event,
2411        cx: &mut ViewContext<Self>,
2412    ) {
2413        match event {
2414            call::room::Event::ParticipantLocationChanged { participant_id }
2415            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2416                self.leader_updated(*participant_id, cx);
2417            }
2418            _ => {}
2419        }
2420    }
2421
2422    pub fn database_id(&self) -> WorkspaceId {
2423        self.database_id
2424    }
2425
2426    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2427        let project = self.project().read(cx);
2428
2429        if project.is_local() {
2430            Some(
2431                project
2432                    .visible_worktrees(cx)
2433                    .map(|worktree| worktree.read(cx).abs_path())
2434                    .collect::<Vec<_>>()
2435                    .into(),
2436            )
2437        } else {
2438            None
2439        }
2440    }
2441
2442    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2443        match member {
2444            Member::Axis(PaneAxis { members, .. }) => {
2445                for child in members.iter() {
2446                    self.remove_panes(child.clone(), cx)
2447                }
2448            }
2449            Member::Pane(pane) => self.remove_pane(pane.clone(), cx),
2450        }
2451    }
2452
2453    fn serialize_workspace(&self, cx: &AppContext) {
2454        fn serialize_pane_handle(
2455            pane_handle: &ViewHandle<Pane>,
2456            cx: &AppContext,
2457        ) -> SerializedPane {
2458            let (items, active) = {
2459                let pane = pane_handle.read(cx);
2460                let active_item_id = pane.active_item().map(|item| item.id());
2461                (
2462                    pane.items()
2463                        .filter_map(|item_handle| {
2464                            Some(SerializedItem {
2465                                kind: Arc::from(item_handle.serialized_item_kind()?),
2466                                item_id: item_handle.id(),
2467                                active: Some(item_handle.id()) == active_item_id,
2468                            })
2469                        })
2470                        .collect::<Vec<_>>(),
2471                    pane.is_active(),
2472                )
2473            };
2474
2475            SerializedPane::new(items, active)
2476        }
2477
2478        fn build_serialized_pane_group(
2479            pane_group: &Member,
2480            cx: &AppContext,
2481        ) -> SerializedPaneGroup {
2482            match pane_group {
2483                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2484                    axis: *axis,
2485                    children: members
2486                        .iter()
2487                        .map(|member| build_serialized_pane_group(member, cx))
2488                        .collect::<Vec<_>>(),
2489                },
2490                Member::Pane(pane_handle) => {
2491                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2492                }
2493            }
2494        }
2495
2496        if let Some(location) = self.location(cx) {
2497            // Load bearing special case:
2498            //  - with_local_workspace() relies on this to not have other stuff open
2499            //    when you open your log
2500            if !location.paths().is_empty() {
2501                let dock_pane = serialize_pane_handle(self.dock.pane(), cx);
2502                let center_group = build_serialized_pane_group(&self.center.root, cx);
2503
2504                let serialized_workspace = SerializedWorkspace {
2505                    id: self.database_id,
2506                    location,
2507                    dock_position: self.dock.position(),
2508                    dock_pane,
2509                    center_group,
2510                    left_sidebar_open: self.left_sidebar.read(cx).is_open(),
2511                    bounds: Default::default(),
2512                    display: Default::default(),
2513                };
2514
2515                cx.background()
2516                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2517                    .detach();
2518            }
2519        }
2520    }
2521
2522    fn load_from_serialized_workspace(
2523        workspace: WeakViewHandle<Workspace>,
2524        serialized_workspace: SerializedWorkspace,
2525        cx: &mut AppContext,
2526    ) {
2527        cx.spawn(|mut cx| async move {
2528            let (project, dock_pane_handle, old_center_pane) =
2529                workspace.read_with(&cx, |workspace, _| {
2530                    (
2531                        workspace.project().clone(),
2532                        workspace.dock_pane().downgrade(),
2533                        workspace.last_active_center_pane.clone(),
2534                    )
2535                })?;
2536
2537            serialized_workspace
2538                .dock_pane
2539                .deserialize_to(
2540                    &project,
2541                    &dock_pane_handle,
2542                    serialized_workspace.id,
2543                    &workspace,
2544                    &mut cx,
2545                )
2546                .await?;
2547
2548            // Traverse the splits tree and add to things
2549            let center_group = serialized_workspace
2550                .center_group
2551                .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2552                .await;
2553
2554            // Remove old panes from workspace panes list
2555            workspace.update(&mut cx, |workspace, cx| {
2556                if let Some((center_group, active_pane)) = center_group {
2557                    workspace.remove_panes(workspace.center.root.clone(), cx);
2558
2559                    // Swap workspace center group
2560                    workspace.center = PaneGroup::with_root(center_group);
2561
2562                    // Change the focus to the workspace first so that we retrigger focus in on the pane.
2563                    cx.focus_self();
2564
2565                    if let Some(active_pane) = active_pane {
2566                        cx.focus(&active_pane);
2567                    } else {
2568                        cx.focus(workspace.panes.last().unwrap());
2569                    }
2570                } else {
2571                    let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
2572                    if let Some(old_center_handle) = old_center_handle {
2573                        cx.focus(&old_center_handle)
2574                    } else {
2575                        cx.focus_self()
2576                    }
2577                }
2578
2579                if workspace.left_sidebar().read(cx).is_open()
2580                    != serialized_workspace.left_sidebar_open
2581                {
2582                    workspace.toggle_sidebar(SidebarSide::Left, cx);
2583                }
2584
2585                // Note that without after_window, the focus_self() and
2586                // the focus the dock generates start generating alternating
2587                // focus due to the deferred execution each triggering each other
2588                cx.after_window_update(move |workspace, cx| {
2589                    Dock::set_dock_position(
2590                        workspace,
2591                        serialized_workspace.dock_position,
2592                        true,
2593                        cx,
2594                    );
2595                });
2596
2597                cx.notify();
2598            })?;
2599
2600            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
2601            workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
2602            anyhow::Ok(())
2603        })
2604        .detach_and_log_err(cx);
2605    }
2606
2607    #[cfg(any(test, feature = "test-support"))]
2608    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
2609        let app_state = Arc::new(AppState {
2610            languages: project.read(cx).languages().clone(),
2611            themes: ThemeRegistry::new((), cx.font_cache().clone()),
2612            client: project.read(cx).client(),
2613            user_store: project.read(cx).user_store(),
2614            fs: project.read(cx).fs().clone(),
2615            build_window_options: |_, _, _| Default::default(),
2616            initialize_workspace: |_, _, _| {},
2617            dock_default_item_factory: |_, _| None,
2618            background_actions: || &[],
2619        });
2620        Self::new(None, 0, project, app_state, cx)
2621    }
2622}
2623
2624fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
2625    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
2626
2627    workspace
2628        .update(cx, |workspace, cx| {
2629            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
2630                workspace.show_notification_once(0, cx, |cx| {
2631                    cx.add_view(|_| {
2632                        MessageNotification::new("Failed to load any database file.")
2633                            .with_click_message("Click to let us know about this error")
2634                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
2635                    })
2636                });
2637            } else {
2638                let backup_path = (*db::BACKUP_DB_PATH).read();
2639                if let Some(backup_path) = backup_path.clone() {
2640                    workspace.show_notification_once(0, cx, move |cx| {
2641                        cx.add_view(move |_| {
2642                            MessageNotification::new(format!(
2643                                "Database file was corrupted. Old database backed up to {}",
2644                                backup_path.display()
2645                            ))
2646                            .with_click_message("Click to show old database in finder")
2647                            .on_click(move |cx| {
2648                                cx.platform().open_url(&backup_path.to_string_lossy())
2649                            })
2650                        })
2651                    });
2652                }
2653            }
2654        })
2655        .log_err();
2656}
2657
2658impl Entity for Workspace {
2659    type Event = Event;
2660}
2661
2662impl View for Workspace {
2663    fn ui_name() -> &'static str {
2664        "Workspace"
2665    }
2666
2667    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2668        let theme = cx.global::<Settings>().theme.clone();
2669        Stack::new()
2670            .with_child(
2671                Flex::column()
2672                    .with_child(self.render_titlebar(&theme, cx))
2673                    .with_child(
2674                        Stack::new()
2675                            .with_child({
2676                                let project = self.project.clone();
2677                                Flex::row()
2678                                    .with_children(
2679                                        if self.left_sidebar.read(cx).active_item().is_some() {
2680                                            Some(
2681                                                ChildView::new(&self.left_sidebar, cx)
2682                                                    .constrained()
2683                                                    .dynamically(|constraint, _, cx| {
2684                                                        SizeConstraint::new(
2685                                                            Vector2F::new(20., constraint.min.y()),
2686                                                            Vector2F::new(
2687                                                                cx.window_size().x() * 0.8,
2688                                                                constraint.max.y(),
2689                                                            ),
2690                                                        )
2691                                                    }),
2692                                            )
2693                                        } else {
2694                                            None
2695                                        },
2696                                    )
2697                                    .with_child(
2698                                        FlexItem::new(
2699                                            Flex::column()
2700                                                .with_child(
2701                                                    FlexItem::new(self.center.render(
2702                                                        &project,
2703                                                        &theme,
2704                                                        &self.follower_states_by_leader,
2705                                                        self.active_call(),
2706                                                        self.active_pane(),
2707                                                        &self.app_state,
2708                                                        cx,
2709                                                    ))
2710                                                    .flex(1., true),
2711                                                )
2712                                                .with_children(self.dock.render(
2713                                                    &theme,
2714                                                    DockAnchor::Bottom,
2715                                                    cx,
2716                                                )),
2717                                        )
2718                                        .flex(1., true),
2719                                    )
2720                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2721                                    .with_children(
2722                                        if self.right_sidebar.read(cx).active_item().is_some() {
2723                                            Some(
2724                                                ChildView::new(&self.right_sidebar, cx)
2725                                                    .constrained()
2726                                                    .dynamically(|constraint, _, cx| {
2727                                                        SizeConstraint::new(
2728                                                            Vector2F::new(20., constraint.min.y()),
2729                                                            Vector2F::new(
2730                                                                cx.window_size().x() * 0.8,
2731                                                                constraint.max.y(),
2732                                                            ),
2733                                                        )
2734                                                    }),
2735                                            )
2736                                        } else {
2737                                            None
2738                                        },
2739                                    )
2740                            })
2741                            .with_child(Overlay::new(
2742                                Stack::new()
2743                                    .with_children(self.dock.render(
2744                                        &theme,
2745                                        DockAnchor::Expanded,
2746                                        cx,
2747                                    ))
2748                                    .with_children(self.modal.as_ref().map(|modal| {
2749                                        ChildView::new(modal, cx)
2750                                            .contained()
2751                                            .with_style(theme.workspace.modal)
2752                                            .aligned()
2753                                            .top()
2754                                    }))
2755                                    .with_children(self.render_notifications(&theme.workspace, cx)),
2756                            ))
2757                            .flex(1.0, true),
2758                    )
2759                    .with_child(ChildView::new(&self.status_bar, cx))
2760                    .contained()
2761                    .with_background_color(theme.workspace.background),
2762            )
2763            .with_children(DragAndDrop::render(cx))
2764            .with_children(self.render_disconnected_overlay(cx))
2765            .into_any_named("workspace")
2766    }
2767
2768    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
2769        if cx.is_self_focused() {
2770            cx.focus(&self.active_pane);
2771        }
2772    }
2773
2774    fn keymap_context(&self, _: &AppContext) -> KeymapContext {
2775        Self::default_keymap_context()
2776    }
2777}
2778
2779impl ViewId {
2780    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
2781        Ok(Self {
2782            creator: message
2783                .creator
2784                .ok_or_else(|| anyhow!("creator is missing"))?,
2785            id: message.id,
2786        })
2787    }
2788
2789    pub(crate) fn to_proto(&self) -> proto::ViewId {
2790        proto::ViewId {
2791            creator: Some(self.creator),
2792            id: self.id,
2793        }
2794    }
2795}
2796
2797pub trait WorkspaceHandle {
2798    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2799}
2800
2801impl WorkspaceHandle for ViewHandle<Workspace> {
2802    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2803        self.read(cx)
2804            .worktrees(cx)
2805            .flat_map(|worktree| {
2806                let worktree_id = worktree.read(cx).id();
2807                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2808                    worktree_id,
2809                    path: f.path.clone(),
2810                })
2811            })
2812            .collect::<Vec<_>>()
2813    }
2814}
2815
2816impl std::fmt::Debug for OpenPaths {
2817    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2818        f.debug_struct("OpenPaths")
2819            .field("paths", &self.paths)
2820            .finish()
2821    }
2822}
2823
2824pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2825
2826pub fn activate_workspace_for_project(
2827    cx: &mut AsyncAppContext,
2828    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2829) -> Option<WeakViewHandle<Workspace>> {
2830    for window_id in cx.window_ids() {
2831        let handle = cx
2832            .update_window(window_id, |cx| {
2833                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
2834                    let project = workspace_handle.read(cx).project.clone();
2835                    if project.update(cx, &predicate) {
2836                        cx.activate_window();
2837                        return Some(workspace_handle.clone());
2838                    }
2839                }
2840                None
2841            })
2842            .flatten();
2843
2844        if let Some(handle) = handle {
2845            return Some(handle.downgrade());
2846        }
2847    }
2848    None
2849}
2850
2851pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
2852    DB.last_workspace().await.log_err().flatten()
2853}
2854
2855#[allow(clippy::type_complexity)]
2856pub fn open_paths(
2857    abs_paths: &[PathBuf],
2858    app_state: &Arc<AppState>,
2859    requesting_window_id: Option<usize>,
2860    cx: &mut AppContext,
2861) -> Task<
2862    Result<(
2863        WeakViewHandle<Workspace>,
2864        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
2865    )>,
2866> {
2867    log::info!("open paths {:?}", abs_paths);
2868
2869    let app_state = app_state.clone();
2870    let abs_paths = abs_paths.to_vec();
2871    cx.spawn(|mut cx| async move {
2872        // Open paths in existing workspace if possible
2873        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
2874            project.contains_paths(&abs_paths, cx)
2875        });
2876
2877        if let Some(existing) = existing {
2878            Ok((
2879                existing.clone(),
2880                existing
2881                    .update(&mut cx, |workspace, cx| {
2882                        workspace.open_paths(abs_paths, true, cx)
2883                    })?
2884                    .await,
2885            ))
2886        } else {
2887            let contains_directory =
2888                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2889                    .await
2890                    .contains(&false);
2891
2892            cx.update(|cx| {
2893                let task =
2894                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx);
2895
2896                cx.spawn(|mut cx| async move {
2897                    let (workspace, items) = task.await;
2898
2899                    workspace.update(&mut cx, |workspace, cx| {
2900                        if contains_directory {
2901                            workspace.toggle_sidebar(SidebarSide::Left, cx);
2902                        }
2903                    })?;
2904
2905                    anyhow::Ok((workspace, items))
2906                })
2907            })
2908            .await
2909        }
2910    })
2911}
2912
2913pub fn open_new(
2914    app_state: &Arc<AppState>,
2915    cx: &mut AppContext,
2916    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
2917) -> Task<()> {
2918    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
2919    cx.spawn(|mut cx| async move {
2920        let (workspace, opened_paths) = task.await;
2921
2922        workspace
2923            .update(&mut cx, |workspace, cx| {
2924                if opened_paths.is_empty() {
2925                    init(workspace, cx)
2926                }
2927            })
2928            .log_err();
2929    })
2930}
2931
2932pub fn join_remote_project(
2933    project_id: u64,
2934    follow_user_id: u64,
2935    app_state: Arc<AppState>,
2936    cx: &mut AppContext,
2937) -> Task<Result<()>> {
2938    cx.spawn(|mut cx| async move {
2939        let existing_workspace = cx
2940            .window_ids()
2941            .into_iter()
2942            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
2943            .find(|workspace| {
2944                cx.read_window(workspace.window_id(), |cx| {
2945                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
2946                })
2947                .unwrap_or(false)
2948            });
2949
2950        let workspace = if let Some(existing_workspace) = existing_workspace {
2951            existing_workspace.downgrade()
2952        } else {
2953            let active_call = cx.read(ActiveCall::global);
2954            let room = active_call
2955                .read_with(&cx, |call, _| call.room().cloned())
2956                .ok_or_else(|| anyhow!("not in a call"))?;
2957            let project = room
2958                .update(&mut cx, |room, cx| {
2959                    room.join_project(
2960                        project_id,
2961                        app_state.languages.clone(),
2962                        app_state.fs.clone(),
2963                        cx,
2964                    )
2965                })
2966                .await?;
2967
2968            let (_, workspace) = cx.add_window(
2969                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
2970                |cx| {
2971                    let mut workspace =
2972                        Workspace::new(Default::default(), 0, project, app_state.clone(), cx);
2973                    (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2974                    workspace
2975                },
2976            );
2977            workspace.downgrade()
2978        };
2979
2980        cx.activate_window(workspace.window_id());
2981        cx.platform().activate(true);
2982
2983        workspace.update(&mut cx, |workspace, cx| {
2984            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
2985                let follow_peer_id = room
2986                    .read(cx)
2987                    .remote_participants()
2988                    .iter()
2989                    .find(|(_, participant)| participant.user.id == follow_user_id)
2990                    .map(|(_, p)| p.peer_id)
2991                    .or_else(|| {
2992                        // If we couldn't follow the given user, follow the host instead.
2993                        let collaborator = workspace
2994                            .project()
2995                            .read(cx)
2996                            .collaborators()
2997                            .values()
2998                            .find(|collaborator| collaborator.replica_id == 0)?;
2999                        Some(collaborator.peer_id)
3000                    });
3001
3002                if let Some(follow_peer_id) = follow_peer_id {
3003                    if !workspace.is_being_followed(follow_peer_id) {
3004                        workspace
3005                            .toggle_follow(follow_peer_id, cx)
3006                            .map(|follow| follow.detach_and_log_err(cx));
3007                    }
3008                }
3009            }
3010        })?;
3011
3012        anyhow::Ok(())
3013    })
3014}
3015
3016pub fn restart(_: &Restart, cx: &mut AppContext) {
3017    let should_confirm = cx.global::<Settings>().confirm_quit;
3018    cx.spawn(|mut cx| async move {
3019        let mut workspaces = cx
3020            .window_ids()
3021            .into_iter()
3022            .filter_map(|window_id| {
3023                Some(
3024                    cx.root_view(window_id)?
3025                        .clone()
3026                        .downcast::<Workspace>()?
3027                        .downgrade(),
3028                )
3029            })
3030            .collect::<Vec<_>>();
3031
3032        // If multiple windows have unsaved changes, and need a save prompt,
3033        // prompt in the active window before switching to a different window.
3034        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3035
3036        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3037            let answer = cx.prompt(
3038                workspace.window_id(),
3039                PromptLevel::Info,
3040                "Are you sure you want to restart?",
3041                &["Restart", "Cancel"],
3042            );
3043
3044            if let Some(mut answer) = answer {
3045                let answer = answer.next().await;
3046                if answer != Some(0) {
3047                    return Ok(());
3048                }
3049            }
3050        }
3051
3052        // If the user cancels any save prompt, then keep the app open.
3053        for workspace in workspaces {
3054            if !workspace
3055                .update(&mut cx, |workspace, cx| {
3056                    workspace.prepare_to_close(true, cx)
3057                })?
3058                .await?
3059            {
3060                return Ok(());
3061            }
3062        }
3063        cx.platform().restart();
3064        anyhow::Ok(())
3065    })
3066    .detach_and_log_err(cx);
3067}
3068
3069fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3070    let mut parts = value.split(',');
3071    let width: usize = parts.next()?.parse().ok()?;
3072    let height: usize = parts.next()?.parse().ok()?;
3073    Some(vec2f(width as f32, height as f32))
3074}
3075
3076#[cfg(test)]
3077mod tests {
3078    use std::{cell::RefCell, rc::Rc};
3079
3080    use crate::item::test::{TestItem, TestItemEvent, TestProjectItem};
3081
3082    use super::*;
3083    use fs::FakeFs;
3084    use gpui::{executor::Deterministic, TestAppContext};
3085    use project::{Project, ProjectEntryId};
3086    use serde_json::json;
3087
3088    #[gpui::test]
3089    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3090        cx.foreground().forbid_parking();
3091        Settings::test_async(cx);
3092
3093        let fs = FakeFs::new(cx.background());
3094        let project = Project::test(fs, [], cx).await;
3095        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3096
3097        // Adding an item with no ambiguity renders the tab without detail.
3098        let item1 = cx.add_view(window_id, |_| {
3099            let mut item = TestItem::new();
3100            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3101            item
3102        });
3103        workspace.update(cx, |workspace, cx| {
3104            workspace.add_item(Box::new(item1.clone()), cx);
3105        });
3106        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3107
3108        // Adding an item that creates ambiguity increases the level of detail on
3109        // both tabs.
3110        let item2 = cx.add_view(window_id, |_| {
3111            let mut item = TestItem::new();
3112            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3113            item
3114        });
3115        workspace.update(cx, |workspace, cx| {
3116            workspace.add_item(Box::new(item2.clone()), cx);
3117        });
3118        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3119        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3120
3121        // Adding an item that creates ambiguity increases the level of detail only
3122        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3123        // we stop at the highest detail available.
3124        let item3 = cx.add_view(window_id, |_| {
3125            let mut item = TestItem::new();
3126            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3127            item
3128        });
3129        workspace.update(cx, |workspace, cx| {
3130            workspace.add_item(Box::new(item3.clone()), cx);
3131        });
3132        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3133        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3134        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3135    }
3136
3137    #[gpui::test]
3138    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3139        cx.foreground().forbid_parking();
3140        Settings::test_async(cx);
3141        let fs = FakeFs::new(cx.background());
3142        fs.insert_tree(
3143            "/root1",
3144            json!({
3145                "one.txt": "",
3146                "two.txt": "",
3147            }),
3148        )
3149        .await;
3150        fs.insert_tree(
3151            "/root2",
3152            json!({
3153                "three.txt": "",
3154            }),
3155        )
3156        .await;
3157
3158        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3159        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3160        let worktree_id = project.read_with(cx, |project, cx| {
3161            project.worktrees(cx).next().unwrap().read(cx).id()
3162        });
3163
3164        let item1 = cx.add_view(window_id, |cx| {
3165            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3166        });
3167        let item2 = cx.add_view(window_id, |cx| {
3168            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3169        });
3170
3171        // Add an item to an empty pane
3172        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3173        project.read_with(cx, |project, cx| {
3174            assert_eq!(
3175                project.active_entry(),
3176                project
3177                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3178                    .map(|e| e.id)
3179            );
3180        });
3181        assert_eq!(
3182            cx.current_window_title(window_id).as_deref(),
3183            Some("one.txt — root1")
3184        );
3185
3186        // Add a second item to a non-empty pane
3187        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3188        assert_eq!(
3189            cx.current_window_title(window_id).as_deref(),
3190            Some("two.txt — root1")
3191        );
3192        project.read_with(cx, |project, cx| {
3193            assert_eq!(
3194                project.active_entry(),
3195                project
3196                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3197                    .map(|e| e.id)
3198            );
3199        });
3200
3201        // Close the active item
3202        workspace
3203            .update(cx, |workspace, cx| {
3204                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
3205            })
3206            .await
3207            .unwrap();
3208        assert_eq!(
3209            cx.current_window_title(window_id).as_deref(),
3210            Some("one.txt — root1")
3211        );
3212        project.read_with(cx, |project, cx| {
3213            assert_eq!(
3214                project.active_entry(),
3215                project
3216                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3217                    .map(|e| e.id)
3218            );
3219        });
3220
3221        // Add a project folder
3222        project
3223            .update(cx, |project, cx| {
3224                project.find_or_create_local_worktree("/root2", true, cx)
3225            })
3226            .await
3227            .unwrap();
3228        assert_eq!(
3229            cx.current_window_title(window_id).as_deref(),
3230            Some("one.txt — root1, root2")
3231        );
3232
3233        // Remove a project folder
3234        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3235        assert_eq!(
3236            cx.current_window_title(window_id).as_deref(),
3237            Some("one.txt — root2")
3238        );
3239    }
3240
3241    #[gpui::test]
3242    async fn test_close_window(cx: &mut TestAppContext) {
3243        cx.foreground().forbid_parking();
3244        Settings::test_async(cx);
3245        let fs = FakeFs::new(cx.background());
3246        fs.insert_tree("/root", json!({ "one": "" })).await;
3247
3248        let project = Project::test(fs, ["root".as_ref()], cx).await;
3249        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3250
3251        // When there are no dirty items, there's nothing to do.
3252        let item1 = cx.add_view(window_id, |_| TestItem::new());
3253        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3254        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3255        assert!(task.await.unwrap());
3256
3257        // When there are dirty untitled items, prompt to save each one. If the user
3258        // cancels any prompt, then abort.
3259        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
3260        let item3 = cx.add_view(window_id, |cx| {
3261            TestItem::new()
3262                .with_dirty(true)
3263                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3264        });
3265        workspace.update(cx, |w, cx| {
3266            w.add_item(Box::new(item2.clone()), cx);
3267            w.add_item(Box::new(item3.clone()), cx);
3268        });
3269        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3270        cx.foreground().run_until_parked();
3271        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3272        cx.foreground().run_until_parked();
3273        assert!(!cx.has_pending_prompt(window_id));
3274        assert!(!task.await.unwrap());
3275    }
3276
3277    #[gpui::test]
3278    async fn test_close_pane_items(cx: &mut TestAppContext) {
3279        cx.foreground().forbid_parking();
3280        Settings::test_async(cx);
3281        let fs = FakeFs::new(cx.background());
3282
3283        let project = Project::test(fs, None, cx).await;
3284        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3285
3286        let item1 = cx.add_view(window_id, |cx| {
3287            TestItem::new()
3288                .with_dirty(true)
3289                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3290        });
3291        let item2 = cx.add_view(window_id, |cx| {
3292            TestItem::new()
3293                .with_dirty(true)
3294                .with_conflict(true)
3295                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3296        });
3297        let item3 = cx.add_view(window_id, |cx| {
3298            TestItem::new()
3299                .with_dirty(true)
3300                .with_conflict(true)
3301                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3302        });
3303        let item4 = cx.add_view(window_id, |cx| {
3304            TestItem::new()
3305                .with_dirty(true)
3306                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3307        });
3308        let pane = workspace.update(cx, |workspace, cx| {
3309            workspace.add_item(Box::new(item1.clone()), cx);
3310            workspace.add_item(Box::new(item2.clone()), cx);
3311            workspace.add_item(Box::new(item3.clone()), cx);
3312            workspace.add_item(Box::new(item4.clone()), cx);
3313            workspace.active_pane().clone()
3314        });
3315
3316        let close_items = workspace.update(cx, |workspace, cx| {
3317            pane.update(cx, |pane, cx| {
3318                pane.activate_item(1, true, true, cx);
3319                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3320            });
3321
3322            let item1_id = item1.id();
3323            let item3_id = item3.id();
3324            let item4_id = item4.id();
3325            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3326                [item1_id, item3_id, item4_id].contains(&id)
3327            })
3328        });
3329        cx.foreground().run_until_parked();
3330
3331        // There's a prompt to save item 1.
3332        pane.read_with(cx, |pane, _| {
3333            assert_eq!(pane.items_len(), 4);
3334            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3335        });
3336        assert!(cx.has_pending_prompt(window_id));
3337
3338        // Confirm saving item 1.
3339        cx.simulate_prompt_answer(window_id, 0);
3340        cx.foreground().run_until_parked();
3341
3342        // Item 1 is saved. There's a prompt to save item 3.
3343        pane.read_with(cx, |pane, cx| {
3344            assert_eq!(item1.read(cx).save_count, 1);
3345            assert_eq!(item1.read(cx).save_as_count, 0);
3346            assert_eq!(item1.read(cx).reload_count, 0);
3347            assert_eq!(pane.items_len(), 3);
3348            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3349        });
3350        assert!(cx.has_pending_prompt(window_id));
3351
3352        // Cancel saving item 3.
3353        cx.simulate_prompt_answer(window_id, 1);
3354        cx.foreground().run_until_parked();
3355
3356        // Item 3 is reloaded. There's a prompt to save item 4.
3357        pane.read_with(cx, |pane, cx| {
3358            assert_eq!(item3.read(cx).save_count, 0);
3359            assert_eq!(item3.read(cx).save_as_count, 0);
3360            assert_eq!(item3.read(cx).reload_count, 1);
3361            assert_eq!(pane.items_len(), 2);
3362            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3363        });
3364        assert!(cx.has_pending_prompt(window_id));
3365
3366        // Confirm saving item 4.
3367        cx.simulate_prompt_answer(window_id, 0);
3368        cx.foreground().run_until_parked();
3369
3370        // There's a prompt for a path for item 4.
3371        cx.simulate_new_path_selection(|_| Some(Default::default()));
3372        close_items.await.unwrap();
3373
3374        // The requested items are closed.
3375        pane.read_with(cx, |pane, cx| {
3376            assert_eq!(item4.read(cx).save_count, 0);
3377            assert_eq!(item4.read(cx).save_as_count, 1);
3378            assert_eq!(item4.read(cx).reload_count, 0);
3379            assert_eq!(pane.items_len(), 1);
3380            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3381        });
3382    }
3383
3384    #[gpui::test]
3385    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3386        cx.foreground().forbid_parking();
3387        Settings::test_async(cx);
3388        let fs = FakeFs::new(cx.background());
3389
3390        let project = Project::test(fs, [], cx).await;
3391        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3392
3393        // Create several workspace items with single project entries, and two
3394        // workspace items with multiple project entries.
3395        let single_entry_items = (0..=4)
3396            .map(|project_entry_id| {
3397                cx.add_view(window_id, |cx| {
3398                    TestItem::new()
3399                        .with_dirty(true)
3400                        .with_project_items(&[TestProjectItem::new(
3401                            project_entry_id,
3402                            &format!("{project_entry_id}.txt"),
3403                            cx,
3404                        )])
3405                })
3406            })
3407            .collect::<Vec<_>>();
3408        let item_2_3 = cx.add_view(window_id, |cx| {
3409            TestItem::new()
3410                .with_dirty(true)
3411                .with_singleton(false)
3412                .with_project_items(&[
3413                    single_entry_items[2].read(cx).project_items[0].clone(),
3414                    single_entry_items[3].read(cx).project_items[0].clone(),
3415                ])
3416        });
3417        let item_3_4 = cx.add_view(window_id, |cx| {
3418            TestItem::new()
3419                .with_dirty(true)
3420                .with_singleton(false)
3421                .with_project_items(&[
3422                    single_entry_items[3].read(cx).project_items[0].clone(),
3423                    single_entry_items[4].read(cx).project_items[0].clone(),
3424                ])
3425        });
3426
3427        // Create two panes that contain the following project entries:
3428        //   left pane:
3429        //     multi-entry items:   (2, 3)
3430        //     single-entry items:  0, 1, 2, 3, 4
3431        //   right pane:
3432        //     single-entry items:  1
3433        //     multi-entry items:   (3, 4)
3434        let left_pane = workspace.update(cx, |workspace, cx| {
3435            let left_pane = workspace.active_pane().clone();
3436            workspace.add_item(Box::new(item_2_3.clone()), cx);
3437            for item in single_entry_items {
3438                workspace.add_item(Box::new(item), cx);
3439            }
3440            left_pane.update(cx, |pane, cx| {
3441                pane.activate_item(2, true, true, cx);
3442            });
3443
3444            workspace
3445                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3446                .unwrap();
3447
3448            left_pane
3449        });
3450
3451        //Need to cause an effect flush in order to respect new focus
3452        workspace.update(cx, |workspace, cx| {
3453            workspace.add_item(Box::new(item_3_4.clone()), cx);
3454            cx.focus(&left_pane);
3455        });
3456
3457        // When closing all of the items in the left pane, we should be prompted twice:
3458        // once for project entry 0, and once for project entry 2. After those two
3459        // prompts, the task should complete.
3460
3461        let close = workspace.update(cx, |workspace, cx| {
3462            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3463        });
3464
3465        cx.foreground().run_until_parked();
3466        left_pane.read_with(cx, |pane, cx| {
3467            assert_eq!(
3468                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3469                &[ProjectEntryId::from_proto(0)]
3470            );
3471        });
3472        cx.simulate_prompt_answer(window_id, 0);
3473
3474        cx.foreground().run_until_parked();
3475        left_pane.read_with(cx, |pane, cx| {
3476            assert_eq!(
3477                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3478                &[ProjectEntryId::from_proto(2)]
3479            );
3480        });
3481        cx.simulate_prompt_answer(window_id, 0);
3482
3483        cx.foreground().run_until_parked();
3484        close.await.unwrap();
3485        left_pane.read_with(cx, |pane, _| {
3486            assert_eq!(pane.items_len(), 0);
3487        });
3488    }
3489
3490    #[gpui::test]
3491    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3492        deterministic.forbid_parking();
3493
3494        Settings::test_async(cx);
3495        let fs = FakeFs::new(cx.background());
3496
3497        let project = Project::test(fs, [], cx).await;
3498        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3499
3500        let item = cx.add_view(window_id, |cx| {
3501            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3502        });
3503        let item_id = item.id();
3504        workspace.update(cx, |workspace, cx| {
3505            workspace.add_item(Box::new(item.clone()), cx);
3506        });
3507
3508        // Autosave on window change.
3509        item.update(cx, |item, cx| {
3510            cx.update_global(|settings: &mut Settings, _| {
3511                settings.autosave = Autosave::OnWindowChange;
3512            });
3513            item.is_dirty = true;
3514        });
3515
3516        // Deactivating the window saves the file.
3517        cx.simulate_window_activation(None);
3518        deterministic.run_until_parked();
3519        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3520
3521        // Autosave on focus change.
3522        item.update(cx, |item, cx| {
3523            cx.focus_self();
3524            cx.update_global(|settings: &mut Settings, _| {
3525                settings.autosave = Autosave::OnFocusChange;
3526            });
3527            item.is_dirty = true;
3528        });
3529
3530        // Blurring the item saves the file.
3531        item.update(cx, |_, cx| cx.blur());
3532        deterministic.run_until_parked();
3533        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3534
3535        // Deactivating the window still saves the file.
3536        cx.simulate_window_activation(Some(window_id));
3537        item.update(cx, |item, cx| {
3538            cx.focus_self();
3539            item.is_dirty = true;
3540        });
3541        cx.simulate_window_activation(None);
3542
3543        deterministic.run_until_parked();
3544        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3545
3546        // Autosave after delay.
3547        item.update(cx, |item, cx| {
3548            cx.update_global(|settings: &mut Settings, _| {
3549                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3550            });
3551            item.is_dirty = true;
3552            cx.emit(TestItemEvent::Edit);
3553        });
3554
3555        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3556        deterministic.advance_clock(Duration::from_millis(250));
3557        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3558
3559        // After delay expires, the file is saved.
3560        deterministic.advance_clock(Duration::from_millis(250));
3561        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3562
3563        // Autosave on focus change, ensuring closing the tab counts as such.
3564        item.update(cx, |item, cx| {
3565            cx.update_global(|settings: &mut Settings, _| {
3566                settings.autosave = Autosave::OnFocusChange;
3567            });
3568            item.is_dirty = true;
3569        });
3570
3571        workspace
3572            .update(cx, |workspace, cx| {
3573                let pane = workspace.active_pane().clone();
3574                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3575            })
3576            .await
3577            .unwrap();
3578        assert!(!cx.has_pending_prompt(window_id));
3579        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3580
3581        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3582        workspace.update(cx, |workspace, cx| {
3583            workspace.add_item(Box::new(item.clone()), cx);
3584        });
3585        item.update(cx, |item, cx| {
3586            item.project_items[0].update(cx, |item, _| {
3587                item.entry_id = None;
3588            });
3589            item.is_dirty = true;
3590            cx.blur();
3591        });
3592        deterministic.run_until_parked();
3593        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3594
3595        // Ensure autosave is prevented for deleted files also when closing the buffer.
3596        let _close_items = workspace.update(cx, |workspace, cx| {
3597            let pane = workspace.active_pane().clone();
3598            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3599        });
3600        deterministic.run_until_parked();
3601        assert!(cx.has_pending_prompt(window_id));
3602        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3603    }
3604
3605    #[gpui::test]
3606    async fn test_pane_navigation(
3607        deterministic: Arc<Deterministic>,
3608        cx: &mut gpui::TestAppContext,
3609    ) {
3610        deterministic.forbid_parking();
3611        Settings::test_async(cx);
3612        let fs = FakeFs::new(cx.background());
3613
3614        let project = Project::test(fs, [], cx).await;
3615        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3616
3617        let item = cx.add_view(window_id, |cx| {
3618            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3619        });
3620        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3621        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3622        let toolbar_notify_count = Rc::new(RefCell::new(0));
3623
3624        workspace.update(cx, |workspace, cx| {
3625            workspace.add_item(Box::new(item.clone()), cx);
3626            let toolbar_notification_count = toolbar_notify_count.clone();
3627            cx.observe(&toolbar, move |_, _, _| {
3628                *toolbar_notification_count.borrow_mut() += 1
3629            })
3630            .detach();
3631        });
3632
3633        pane.read_with(cx, |pane, _| {
3634            assert!(!pane.can_navigate_backward());
3635            assert!(!pane.can_navigate_forward());
3636        });
3637
3638        item.update(cx, |item, cx| {
3639            item.set_state("one".to_string(), cx);
3640        });
3641
3642        // Toolbar must be notified to re-render the navigation buttons
3643        assert_eq!(*toolbar_notify_count.borrow(), 1);
3644
3645        pane.read_with(cx, |pane, _| {
3646            assert!(pane.can_navigate_backward());
3647            assert!(!pane.can_navigate_forward());
3648        });
3649
3650        workspace
3651            .update(cx, |workspace, cx| {
3652                Pane::go_back(workspace, Some(pane.downgrade()), cx)
3653            })
3654            .await
3655            .unwrap();
3656
3657        assert_eq!(*toolbar_notify_count.borrow(), 3);
3658        pane.read_with(cx, |pane, _| {
3659            assert!(!pane.can_navigate_backward());
3660            assert!(pane.can_navigate_forward());
3661        });
3662    }
3663}