workspace.rs

   1pub mod dock;
   2pub mod item;
   3mod modal_layer;
   4pub mod notifications;
   5pub mod pane;
   6pub mod pane_group;
   7mod persistence;
   8pub mod searchable;
   9pub mod shared_screen;
  10mod status_bar;
  11mod toolbar;
  12mod workspace_settings;
  13
  14use anyhow::{anyhow, Context as _, Result};
  15use call::{call_settings::CallSettings, ActiveCall};
  16use client::{
  17    proto::{self, ErrorCode, PeerId},
  18    ChannelId, Client, ErrorExt, ProjectId, Status, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use derive_more::{Deref, DerefMut};
  22use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  23use futures::{
  24    channel::{mpsc, oneshot},
  25    future::try_join_all,
  26    Future, FutureExt, StreamExt,
  27};
  28use gpui::{
  29    actions, canvas, impl_actions, point, size, Action, AnyElement, AnyView, AnyWeakView,
  30    AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DragMoveEvent, Entity as _, EntityId,
  31    EventEmitter, FocusHandle, FocusableView, Global, GlobalPixels, KeyContext, Keystroke,
  32    LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render,
  33    Size, Subscription, Task, View, WeakView, WindowHandle, WindowOptions,
  34};
  35use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
  36use itertools::Itertools;
  37use language::{LanguageRegistry, Rope};
  38use lazy_static::lazy_static;
  39pub use modal_layer::*;
  40use node_runtime::NodeRuntime;
  41use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  42pub use pane::*;
  43pub use pane_group::*;
  44use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB};
  45pub use persistence::{
  46    model::{ItemId, WorkspaceLocation},
  47    WorkspaceDb, DB as WORKSPACE_DB,
  48};
  49use postage::stream::Stream;
  50use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  51use serde::Deserialize;
  52use settings::Settings;
  53use shared_screen::SharedScreen;
  54use sqlez::{
  55    bindable::{Bind, Column, StaticColumnCount},
  56    statement::Statement,
  57};
  58use status_bar::StatusBar;
  59pub use status_bar::StatusItemView;
  60use std::{
  61    any::TypeId,
  62    borrow::Cow,
  63    cell::RefCell,
  64    cmp,
  65    collections::hash_map::DefaultHasher,
  66    env,
  67    hash::{Hash, Hasher},
  68    path::{Path, PathBuf},
  69    rc::Rc,
  70    sync::{atomic::AtomicUsize, Arc, Weak},
  71    time::Duration,
  72};
  73use task::SpawnInTerminal;
  74use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
  75pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  76pub use ui;
  77use ui::{
  78    div, Context as _, Div, Element, ElementContext, InteractiveElement as _, IntoElement, Label,
  79    ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _,
  80    WindowContext,
  81};
  82use util::ResultExt;
  83use uuid::Uuid;
  84pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
  85
  86use crate::persistence::{
  87    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  88    SerializedAxis,
  89};
  90
  91lazy_static! {
  92    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
  93        .ok()
  94        .as_deref()
  95        .and_then(parse_pixel_size_env_var);
  96    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
  97        .ok()
  98        .as_deref()
  99        .and_then(parse_pixel_position_env_var);
 100}
 101
 102#[derive(Clone, PartialEq)]
 103pub struct RemoveWorktreeFromProject(pub WorktreeId);
 104
 105actions!(
 106    workspace,
 107    [
 108        Open,
 109        NewFile,
 110        NewWindow,
 111        CloseWindow,
 112        AddFolderToProject,
 113        Unfollow,
 114        SaveAs,
 115        SaveWithoutFormat,
 116        ReloadActiveItem,
 117        ActivatePreviousPane,
 118        ActivateNextPane,
 119        FollowNextCollaborator,
 120        NewTerminal,
 121        NewCenterTerminal,
 122        NewSearch,
 123        Feedback,
 124        Restart,
 125        Welcome,
 126        ToggleZoom,
 127        ToggleLeftDock,
 128        ToggleRightDock,
 129        ToggleBottomDock,
 130        CloseAllDocks,
 131    ]
 132);
 133
 134#[derive(Clone, PartialEq)]
 135pub struct OpenPaths {
 136    pub paths: Vec<PathBuf>,
 137}
 138
 139#[derive(Clone, Deserialize, PartialEq)]
 140pub struct ActivatePane(pub usize);
 141
 142#[derive(Clone, Deserialize, PartialEq)]
 143pub struct ActivatePaneInDirection(pub SplitDirection);
 144
 145#[derive(Clone, Deserialize, PartialEq)]
 146pub struct SwapPaneInDirection(pub SplitDirection);
 147
 148#[derive(Clone, Deserialize, PartialEq)]
 149pub struct NewFileInDirection(pub SplitDirection);
 150
 151#[derive(Clone, PartialEq, Debug, Deserialize)]
 152#[serde(rename_all = "camelCase")]
 153pub struct SaveAll {
 154    pub save_intent: Option<SaveIntent>,
 155}
 156
 157#[derive(Clone, PartialEq, Debug, Deserialize)]
 158#[serde(rename_all = "camelCase")]
 159pub struct Save {
 160    pub save_intent: Option<SaveIntent>,
 161}
 162
 163#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 164#[serde(rename_all = "camelCase")]
 165pub struct CloseAllItemsAndPanes {
 166    pub save_intent: Option<SaveIntent>,
 167}
 168
 169#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 170#[serde(rename_all = "camelCase")]
 171pub struct CloseInactiveTabsAndPanes {
 172    pub save_intent: Option<SaveIntent>,
 173}
 174
 175#[derive(Clone, Deserialize, PartialEq)]
 176pub struct SendKeystrokes(pub String);
 177
 178impl_actions!(
 179    workspace,
 180    [
 181        ActivatePane,
 182        ActivatePaneInDirection,
 183        CloseAllItemsAndPanes,
 184        CloseInactiveTabsAndPanes,
 185        NewFileInDirection,
 186        OpenTerminal,
 187        Save,
 188        SaveAll,
 189        SwapPaneInDirection,
 190        SendKeystrokes,
 191    ]
 192);
 193
 194#[derive(Deserialize)]
 195pub struct Toast {
 196    id: usize,
 197    msg: Cow<'static, str>,
 198    #[serde(skip)]
 199    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 200}
 201
 202impl Toast {
 203    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 204        Toast {
 205            id,
 206            msg: msg.into(),
 207            on_click: None,
 208        }
 209    }
 210
 211    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 212    where
 213        M: Into<Cow<'static, str>>,
 214        F: Fn(&mut WindowContext) + 'static,
 215    {
 216        self.on_click = Some((message.into(), Arc::new(on_click)));
 217        self
 218    }
 219}
 220
 221impl PartialEq for Toast {
 222    fn eq(&self, other: &Self) -> bool {
 223        self.id == other.id
 224            && self.msg == other.msg
 225            && self.on_click.is_some() == other.on_click.is_some()
 226    }
 227}
 228
 229impl Clone for Toast {
 230    fn clone(&self) -> Self {
 231        Toast {
 232            id: self.id,
 233            msg: self.msg.clone(),
 234            on_click: self.on_click.clone(),
 235        }
 236    }
 237}
 238
 239#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 240pub struct OpenTerminal {
 241    pub working_directory: PathBuf,
 242}
 243
 244#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
 245pub struct WorkspaceId(i64);
 246
 247impl StaticColumnCount for WorkspaceId {}
 248impl Bind for WorkspaceId {
 249    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
 250        self.0.bind(statement, start_index)
 251    }
 252}
 253impl Column for WorkspaceId {
 254    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
 255        i64::column(statement, start_index)
 256            .map(|(i, next_index)| (Self(i), next_index))
 257            .with_context(|| format!("Failed to read WorkspaceId at index {start_index}"))
 258    }
 259}
 260pub fn init_settings(cx: &mut AppContext) {
 261    WorkspaceSettings::register(cx);
 262    ItemSettings::register(cx);
 263}
 264
 265pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 266    init_settings(cx);
 267    notifications::init(cx);
 268
 269    cx.on_action(Workspace::close_global);
 270    cx.on_action(restart);
 271
 272    cx.on_action({
 273        let app_state = Arc::downgrade(&app_state);
 274        move |_: &Open, cx: &mut AppContext| {
 275            let paths = cx.prompt_for_paths(PathPromptOptions {
 276                files: true,
 277                directories: true,
 278                multiple: true,
 279            });
 280
 281            if let Some(app_state) = app_state.upgrade() {
 282                cx.spawn(move |cx| async move {
 283                    if let Some(paths) = paths.await.log_err().flatten() {
 284                        cx.update(|cx| {
 285                            open_paths(&paths, app_state, OpenOptions::default(), cx)
 286                                .detach_and_log_err(cx)
 287                        })
 288                        .ok();
 289                    }
 290                })
 291                .detach();
 292            }
 293        }
 294    });
 295}
 296
 297#[derive(Clone, Default, Deref, DerefMut)]
 298struct ProjectItemOpeners(Vec<ProjectItemOpener>);
 299
 300type ProjectItemOpener = fn(
 301    &Model<Project>,
 302    &ProjectPath,
 303    &mut WindowContext,
 304)
 305    -> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>;
 306
 307type WorkspaceItemBuilder = Box<dyn FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
 308
 309impl Global for ProjectItemOpeners {}
 310
 311/// Registers a [ProjectItem] for the app. When opening a file, all the registered
 312/// items will get a chance to open the file, starting from the project item that
 313/// was added last.
 314pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 315    let builders = cx.default_global::<ProjectItemOpeners>();
 316    builders.push(|project, project_path, cx| {
 317        let project_item = <I::Item as project::Item>::try_open(&project, project_path, cx)?;
 318        let project = project.clone();
 319        Some(cx.spawn(|cx| async move {
 320            let project_item = project_item.await?;
 321            let project_entry_id: Option<ProjectEntryId> =
 322                project_item.read_with(&cx, |item, cx| project::Item::entry_id(item, cx))?;
 323            let build_workspace_item = Box::new(|cx: &mut ViewContext<Pane>| {
 324                Box::new(cx.new_view(|cx| I::for_project_item(project, project_item, cx)))
 325                    as Box<dyn ItemHandle>
 326            }) as Box<_>;
 327            Ok((project_entry_id, build_workspace_item))
 328        }))
 329    });
 330}
 331
 332type FollowableItemBuilder = fn(
 333    View<Pane>,
 334    View<Workspace>,
 335    ViewId,
 336    &mut Option<proto::view::Variant>,
 337    &mut WindowContext,
 338) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 339
 340#[derive(Default, Deref, DerefMut)]
 341struct FollowableItemBuilders(
 342    HashMap<
 343        TypeId,
 344        (
 345            FollowableItemBuilder,
 346            fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 347        ),
 348    >,
 349);
 350
 351impl Global for FollowableItemBuilders {}
 352
 353pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 354    let builders = cx.default_global::<FollowableItemBuilders>();
 355    builders.insert(
 356        TypeId::of::<I>(),
 357        (
 358            |pane, workspace, id, state, cx| {
 359                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 360                    cx.foreground_executor()
 361                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 362                })
 363            },
 364            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 365        ),
 366    );
 367}
 368
 369#[derive(Default, Deref, DerefMut)]
 370struct ItemDeserializers(
 371    HashMap<
 372        Arc<str>,
 373        fn(
 374            Model<Project>,
 375            WeakView<Workspace>,
 376            WorkspaceId,
 377            ItemId,
 378            &mut ViewContext<Pane>,
 379        ) -> Task<Result<Box<dyn ItemHandle>>>,
 380    >,
 381);
 382
 383impl Global for ItemDeserializers {}
 384
 385pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 386    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 387        let deserializers = cx.default_global::<ItemDeserializers>();
 388        deserializers.insert(
 389            Arc::from(serialized_item_kind),
 390            |project, workspace, workspace_id, item_id, cx| {
 391                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 392                cx.foreground_executor()
 393                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 394            },
 395        );
 396    }
 397}
 398
 399pub struct AppState {
 400    pub languages: Arc<LanguageRegistry>,
 401    pub client: Arc<Client>,
 402    pub user_store: Model<UserStore>,
 403    pub workspace_store: Model<WorkspaceStore>,
 404    pub fs: Arc<dyn fs::Fs>,
 405    pub build_window_options: fn(Option<Uuid>, &mut AppContext) -> WindowOptions,
 406    pub node_runtime: Arc<dyn NodeRuntime>,
 407}
 408
 409struct GlobalAppState(Weak<AppState>);
 410
 411impl Global for GlobalAppState {}
 412
 413pub struct WorkspaceStore {
 414    workspaces: HashSet<WindowHandle<Workspace>>,
 415    client: Arc<Client>,
 416    _subscriptions: Vec<client::Subscription>,
 417}
 418
 419#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 420struct Follower {
 421    project_id: Option<u64>,
 422    peer_id: PeerId,
 423}
 424
 425impl AppState {
 426    pub fn global(cx: &AppContext) -> Weak<Self> {
 427        cx.global::<GlobalAppState>().0.clone()
 428    }
 429    pub fn try_global(cx: &AppContext) -> Option<Weak<Self>> {
 430        cx.try_global::<GlobalAppState>()
 431            .map(|state| state.0.clone())
 432    }
 433    pub fn set_global(state: Weak<AppState>, cx: &mut AppContext) {
 434        cx.set_global(GlobalAppState(state));
 435    }
 436
 437    #[cfg(any(test, feature = "test-support"))]
 438    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 439        use node_runtime::FakeNodeRuntime;
 440        use settings::SettingsStore;
 441        use ui::Context as _;
 442
 443        if !cx.has_global::<SettingsStore>() {
 444            let settings_store = SettingsStore::test(cx);
 445            cx.set_global(settings_store);
 446        }
 447
 448        let fs = fs::FakeFs::new(cx.background_executor().clone());
 449        let languages = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
 450        let clock = Arc::new(clock::FakeSystemClock::default());
 451        let http_client = util::http::FakeHttpClient::with_404_response();
 452        let client = Client::new(clock, http_client.clone(), cx);
 453        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 454        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 455
 456        theme::init(theme::LoadThemes::JustBase, cx);
 457        client::init(&client, cx);
 458        crate::init_settings(cx);
 459
 460        Arc::new(Self {
 461            client,
 462            fs,
 463            languages,
 464            user_store,
 465            workspace_store,
 466            node_runtime: FakeNodeRuntime::new(),
 467            build_window_options: |_, _| Default::default(),
 468        })
 469    }
 470}
 471
 472struct DelayedDebouncedEditAction {
 473    task: Option<Task<()>>,
 474    cancel_channel: Option<oneshot::Sender<()>>,
 475}
 476
 477impl DelayedDebouncedEditAction {
 478    fn new() -> DelayedDebouncedEditAction {
 479        DelayedDebouncedEditAction {
 480            task: None,
 481            cancel_channel: None,
 482        }
 483    }
 484
 485    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 486    where
 487        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 488    {
 489        if let Some(channel) = self.cancel_channel.take() {
 490            _ = channel.send(());
 491        }
 492
 493        let (sender, mut receiver) = oneshot::channel::<()>();
 494        self.cancel_channel = Some(sender);
 495
 496        let previous_task = self.task.take();
 497        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 498            let mut timer = cx.background_executor().timer(delay).fuse();
 499            if let Some(previous_task) = previous_task {
 500                previous_task.await;
 501            }
 502
 503            futures::select_biased! {
 504                _ = receiver => return,
 505                    _ = timer => {}
 506            }
 507
 508            if let Some(result) = workspace
 509                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 510                .log_err()
 511            {
 512                result.await.log_err();
 513            }
 514        }));
 515    }
 516}
 517
 518pub enum Event {
 519    PaneAdded(View<Pane>),
 520    ContactRequestedJoin(u64),
 521    WorkspaceCreated(WeakView<Workspace>),
 522    SpawnTask(SpawnInTerminal),
 523}
 524
 525pub enum OpenVisible {
 526    All,
 527    None,
 528    OnlyFiles,
 529    OnlyDirectories,
 530}
 531
 532/// Collects everything project-related for a certain window opened.
 533/// In some way, is a counterpart of a window, as the [`WindowHandle`] could be downcast into `Workspace`.
 534///
 535/// A `Workspace` usually consists of 1 or more projects, a central pane group, 3 docks and a status bar.
 536/// The `Workspace` owns everybody's state and serves as a default, "global context",
 537/// that can be used to register a global action to be triggered from any place in the window.
 538pub struct Workspace {
 539    weak_self: WeakView<Self>,
 540    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 541    zoomed: Option<AnyWeakView>,
 542    zoomed_position: Option<DockPosition>,
 543    center: PaneGroup,
 544    left_dock: View<Dock>,
 545    bottom_dock: View<Dock>,
 546    right_dock: View<Dock>,
 547    panes: Vec<View<Pane>>,
 548    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 549    active_pane: View<Pane>,
 550    last_active_center_pane: Option<WeakView<Pane>>,
 551    last_active_view_id: Option<proto::ViewId>,
 552    status_bar: View<StatusBar>,
 553    modal_layer: View<ModalLayer>,
 554    titlebar_item: Option<AnyView>,
 555    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 556    project: Model<Project>,
 557    follower_states: HashMap<View<Pane>, FollowerState>,
 558    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 559    window_edited: bool,
 560    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 561    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 562    database_id: WorkspaceId,
 563    app_state: Arc<AppState>,
 564    dispatching_keystrokes: Rc<RefCell<Vec<Keystroke>>>,
 565    _subscriptions: Vec<Subscription>,
 566    _apply_leader_updates: Task<Result<()>>,
 567    _observe_current_user: Task<Result<()>>,
 568    _schedule_serialize: Option<Task<()>>,
 569    pane_history_timestamp: Arc<AtomicUsize>,
 570    bounds: Bounds<Pixels>,
 571}
 572
 573impl EventEmitter<Event> for Workspace {}
 574
 575#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 576pub struct ViewId {
 577    pub creator: PeerId,
 578    pub id: u64,
 579}
 580
 581#[derive(Default)]
 582struct FollowerState {
 583    leader_id: PeerId,
 584    active_view_id: Option<ViewId>,
 585    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 586}
 587
 588impl Workspace {
 589    pub fn new(
 590        workspace_id: WorkspaceId,
 591        project: Model<Project>,
 592        app_state: Arc<AppState>,
 593        cx: &mut ViewContext<Self>,
 594    ) -> Self {
 595        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 596        cx.subscribe(&project, move |this, _, event, cx| {
 597            match event {
 598                project::Event::RemoteIdChanged(_) => {
 599                    this.update_window_title(cx);
 600                }
 601
 602                project::Event::CollaboratorLeft(peer_id) => {
 603                    this.collaborator_left(*peer_id, cx);
 604                }
 605
 606                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 607                    this.update_window_title(cx);
 608                    let workspace_serialization = this.serialize_workspace(cx);
 609                    cx.spawn(|workspace, mut cx| async move {
 610                        workspace_serialization.await;
 611                        workspace
 612                            .update(&mut cx, |workspace, cx| {
 613                                workspace.refresh_recent_documents(cx)
 614                            })?
 615                            .await
 616                    })
 617                    .detach_and_log_err(cx)
 618                }
 619
 620                project::Event::DisconnectedFromHost => {
 621                    this.update_window_edited(cx);
 622                    let panes_to_unfollow: Vec<View<Pane>> =
 623                        this.follower_states.keys().map(|k| k.clone()).collect();
 624                    for pane in panes_to_unfollow {
 625                        this.unfollow(&pane, cx);
 626                    }
 627                    cx.disable_focus();
 628                }
 629
 630                project::Event::Closed => {
 631                    cx.remove_window();
 632                }
 633
 634                project::Event::DeletedEntry(entry_id) => {
 635                    for pane in this.panes.iter() {
 636                        pane.update(cx, |pane, cx| {
 637                            pane.handle_deleted_project_item(*entry_id, cx)
 638                        });
 639                    }
 640                }
 641
 642                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 643                    cx.new_view(|_| MessageNotification::new(message.clone()))
 644                }),
 645
 646                project::Event::LanguageServerPrompt(request) => {
 647                    let mut hasher = DefaultHasher::new();
 648                    request.message.as_str().hash(&mut hasher);
 649                    let id = hasher.finish();
 650
 651                    this.show_notification(id as usize, cx, |cx| {
 652                        cx.new_view(|_| notifications::LanguageServerPrompt::new(request.clone()))
 653                    });
 654                }
 655
 656                _ => {}
 657            }
 658            cx.notify()
 659        })
 660        .detach();
 661
 662        cx.on_focus_lost(|this, cx| {
 663            let focus_handle = this.focus_handle(cx);
 664            cx.focus(&focus_handle);
 665        })
 666        .detach();
 667
 668        let weak_handle = cx.view().downgrade();
 669        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 670
 671        let center_pane = cx.new_view(|cx| {
 672            Pane::new(
 673                weak_handle.clone(),
 674                project.clone(),
 675                pane_history_timestamp.clone(),
 676                None,
 677                NewFile.boxed_clone(),
 678                cx,
 679            )
 680        });
 681        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 682
 683        cx.focus_view(&center_pane);
 684        cx.emit(Event::PaneAdded(center_pane.clone()));
 685
 686        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 687        app_state.workspace_store.update(cx, |store, _| {
 688            store.workspaces.insert(window_handle);
 689        });
 690
 691        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 692        let mut connection_status = app_state.client.status();
 693        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 694            current_user.next().await;
 695            connection_status.next().await;
 696            let mut stream =
 697                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 698
 699            while stream.recv().await.is_some() {
 700                this.update(&mut cx, |_, cx| cx.notify())?;
 701            }
 702            anyhow::Ok(())
 703        });
 704
 705        // All leader updates are enqueued and then processed in a single task, so
 706        // that each asynchronous operation can be run in order.
 707        let (leader_updates_tx, mut leader_updates_rx) =
 708            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 709        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 710            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 711                Self::process_leader_update(&this, leader_id, update, &mut cx)
 712                    .await
 713                    .log_err();
 714            }
 715
 716            Ok(())
 717        });
 718
 719        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 720
 721        let left_dock = Dock::new(DockPosition::Left, cx);
 722        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
 723        let right_dock = Dock::new(DockPosition::Right, cx);
 724        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
 725        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 726        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
 727        let status_bar = cx.new_view(|cx| {
 728            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 729            status_bar.add_left_item(left_dock_buttons, cx);
 730            status_bar.add_right_item(right_dock_buttons, cx);
 731            status_bar.add_right_item(bottom_dock_buttons, cx);
 732            status_bar
 733        });
 734
 735        let modal_layer = cx.new_view(|_| ModalLayer::new());
 736
 737        let mut active_call = None;
 738        if let Some(call) = ActiveCall::try_global(cx) {
 739            let call = call.clone();
 740            let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
 741            active_call = Some((call, subscriptions));
 742        }
 743
 744        let subscriptions = vec![
 745            cx.observe_window_activation(Self::on_window_activation_changed),
 746            cx.observe_window_bounds(move |_, cx| {
 747                if let Some(display) = cx.display() {
 748                    // Transform fixed bounds to be stored in terms of the containing display
 749                    let mut window_bounds = cx.window_bounds();
 750                    let display_bounds = display.bounds();
 751                    window_bounds.origin.x -= display_bounds.origin.x;
 752                    window_bounds.origin.y -= display_bounds.origin.y;
 753                    let fullscreen = cx.is_fullscreen();
 754
 755                    if let Some(display_uuid) = display.uuid().log_err() {
 756                        // Only update the window bounds when not full screen,
 757                        // so we can remember the last non-fullscreen bounds
 758                        // across restarts
 759                        if fullscreen {
 760                            cx.background_executor()
 761                                .spawn(DB.set_fullscreen(workspace_id, true))
 762                                .detach_and_log_err(cx);
 763                        } else if !cx.is_minimized() {
 764                            cx.background_executor()
 765                                .spawn(DB.set_fullscreen(workspace_id, false))
 766                                .detach_and_log_err(cx);
 767                            cx.background_executor()
 768                                .spawn(DB.set_window_bounds(
 769                                    workspace_id,
 770                                    SerializedWindowsBounds(window_bounds),
 771                                    display_uuid,
 772                                ))
 773                                .detach_and_log_err(cx);
 774                        }
 775                    }
 776                }
 777                cx.notify();
 778            }),
 779            cx.observe_window_appearance(|_, cx| {
 780                let window_appearance = cx.appearance();
 781
 782                *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
 783
 784                ThemeSettings::reload_current_theme(cx);
 785            }),
 786            cx.observe(&left_dock, |this, _, cx| {
 787                this.serialize_workspace(cx).detach();
 788                cx.notify();
 789            }),
 790            cx.observe(&bottom_dock, |this, _, cx| {
 791                this.serialize_workspace(cx).detach();
 792                cx.notify();
 793            }),
 794            cx.observe(&right_dock, |this, _, cx| {
 795                this.serialize_workspace(cx).detach();
 796                cx.notify();
 797            }),
 798            cx.on_release(|this, window, cx| {
 799                this.app_state.workspace_store.update(cx, |store, _| {
 800                    let window = window.downcast::<Self>().unwrap();
 801                    store.workspaces.remove(&window);
 802                })
 803            }),
 804        ];
 805
 806        cx.defer(|this, cx| {
 807            this.update_window_title(cx);
 808        });
 809        Workspace {
 810            weak_self: weak_handle.clone(),
 811            zoomed: None,
 812            zoomed_position: None,
 813            center: PaneGroup::new(center_pane.clone()),
 814            panes: vec![center_pane.clone()],
 815            panes_by_item: Default::default(),
 816            active_pane: center_pane.clone(),
 817            last_active_center_pane: Some(center_pane.downgrade()),
 818            last_active_view_id: None,
 819            status_bar,
 820            modal_layer,
 821            titlebar_item: None,
 822            notifications: Default::default(),
 823            left_dock,
 824            bottom_dock,
 825            right_dock,
 826            project: project.clone(),
 827            follower_states: Default::default(),
 828            last_leaders_by_pane: Default::default(),
 829            dispatching_keystrokes: Default::default(),
 830            window_edited: false,
 831            active_call,
 832            database_id: workspace_id,
 833            app_state,
 834            _observe_current_user,
 835            _apply_leader_updates,
 836            _schedule_serialize: None,
 837            leader_updates_tx,
 838            _subscriptions: subscriptions,
 839            pane_history_timestamp,
 840            workspace_actions: Default::default(),
 841            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 842            bounds: Default::default(),
 843        }
 844    }
 845
 846    fn new_local(
 847        abs_paths: Vec<PathBuf>,
 848        app_state: Arc<AppState>,
 849        requesting_window: Option<WindowHandle<Workspace>>,
 850        cx: &mut AppContext,
 851    ) -> Task<
 852        anyhow::Result<(
 853            WindowHandle<Workspace>,
 854            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 855        )>,
 856    > {
 857        let project_handle = Project::local(
 858            app_state.client.clone(),
 859            app_state.node_runtime.clone(),
 860            app_state.user_store.clone(),
 861            app_state.languages.clone(),
 862            app_state.fs.clone(),
 863            cx,
 864        );
 865
 866        cx.spawn(|mut cx| async move {
 867            let serialized_workspace: Option<SerializedWorkspace> =
 868                persistence::DB.workspace_for_roots(abs_paths.as_slice());
 869
 870            let paths_to_open = Arc::new(abs_paths);
 871
 872            // Get project paths for all of the abs_paths
 873            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 874            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 875                Vec::with_capacity(paths_to_open.len());
 876            for path in paths_to_open.iter().cloned() {
 877                if let Some((worktree, project_entry)) = cx
 878                    .update(|cx| {
 879                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 880                    })?
 881                    .await
 882                    .log_err()
 883                {
 884                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 885                    project_paths.push((path, Some(project_entry)));
 886                } else {
 887                    project_paths.push((path, None));
 888                }
 889            }
 890
 891            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 892                serialized_workspace.id
 893            } else {
 894                DB.next_id().await.unwrap_or_else(|_| Default::default())
 895            };
 896
 897            let window = if let Some(window) = requesting_window {
 898                cx.update_window(window.into(), |_, cx| {
 899                    cx.replace_root_view(|cx| {
 900                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 901                    });
 902                })?;
 903                window
 904            } else {
 905                let window_bounds_override = window_bounds_env_override(&cx);
 906
 907                let (bounds, display, fullscreen) = if let Some(bounds) = window_bounds_override {
 908                    (Some(bounds), None, false)
 909                } else {
 910                    let restorable_bounds = serialized_workspace
 911                        .as_ref()
 912                        .and_then(|workspace| {
 913                            Some((workspace.display?, workspace.bounds?, workspace.fullscreen))
 914                        })
 915                        .or_else(|| {
 916                            let (display, bounds, fullscreen) = DB.last_window().log_err()?;
 917                            Some((display?, bounds?.0, fullscreen.unwrap_or(false)))
 918                        });
 919
 920                    if let Some((serialized_display, mut bounds, fullscreen)) = restorable_bounds {
 921                        // Stored bounds are relative to the containing display.
 922                        // So convert back to global coordinates if that screen still exists
 923                        let screen_bounds = cx
 924                            .update(|cx| {
 925                                cx.displays()
 926                                    .into_iter()
 927                                    .find(|display| display.uuid().ok() == Some(serialized_display))
 928                            })
 929                            .ok()
 930                            .flatten()
 931                            .map(|screen| screen.bounds());
 932
 933                        if let Some(screen_bounds) = screen_bounds {
 934                            bounds.origin.x += screen_bounds.origin.x;
 935                            bounds.origin.y += screen_bounds.origin.y;
 936                        }
 937
 938                        (Some(bounds), Some(serialized_display), fullscreen)
 939                    } else {
 940                        (None, None, false)
 941                    }
 942                };
 943
 944                // Use the serialized workspace to construct the new window
 945                let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?;
 946                options.bounds = bounds;
 947                options.fullscreen = fullscreen;
 948                cx.open_window(options, {
 949                    let app_state = app_state.clone();
 950                    let project_handle = project_handle.clone();
 951                    move |cx| {
 952                        cx.new_view(|cx| {
 953                            Workspace::new(workspace_id, project_handle, app_state, cx)
 954                        })
 955                    }
 956                })?
 957            };
 958
 959            notify_if_database_failed(window, &mut cx);
 960            let opened_items = window
 961                .update(&mut cx, |_workspace, cx| {
 962                    open_items(serialized_workspace, project_paths, app_state, cx)
 963                })?
 964                .await
 965                .unwrap_or_default();
 966
 967            window
 968                .update(&mut cx, |workspace, cx| {
 969                    workspace
 970                        .refresh_recent_documents(cx)
 971                        .detach_and_log_err(cx);
 972                    cx.activate_window()
 973                })
 974                .log_err();
 975            Ok((window, opened_items))
 976        })
 977    }
 978
 979    pub fn weak_handle(&self) -> WeakView<Self> {
 980        self.weak_self.clone()
 981    }
 982
 983    pub fn left_dock(&self) -> &View<Dock> {
 984        &self.left_dock
 985    }
 986
 987    pub fn bottom_dock(&self) -> &View<Dock> {
 988        &self.bottom_dock
 989    }
 990
 991    pub fn right_dock(&self) -> &View<Dock> {
 992        &self.right_dock
 993    }
 994
 995    pub fn is_edited(&self) -> bool {
 996        self.window_edited
 997    }
 998
 999    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut WindowContext) {
1000        let dock = match panel.position(cx) {
1001            DockPosition::Left => &self.left_dock,
1002            DockPosition::Bottom => &self.bottom_dock,
1003            DockPosition::Right => &self.right_dock,
1004        };
1005
1006        dock.update(cx, |dock, cx| {
1007            dock.add_panel(panel, self.weak_self.clone(), cx)
1008        });
1009    }
1010
1011    pub fn status_bar(&self) -> &View<StatusBar> {
1012        &self.status_bar
1013    }
1014
1015    pub fn app_state(&self) -> &Arc<AppState> {
1016        &self.app_state
1017    }
1018
1019    pub fn user_store(&self) -> &Model<UserStore> {
1020        &self.app_state.user_store
1021    }
1022
1023    pub fn project(&self) -> &Model<Project> {
1024        &self.project
1025    }
1026
1027    pub fn recent_navigation_history(
1028        &self,
1029        limit: Option<usize>,
1030        cx: &AppContext,
1031    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
1032        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
1033        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
1034        for pane in &self.panes {
1035            let pane = pane.read(cx);
1036            pane.nav_history()
1037                .for_each_entry(cx, |entry, (project_path, fs_path)| {
1038                    if let Some(fs_path) = &fs_path {
1039                        abs_paths_opened
1040                            .entry(fs_path.clone())
1041                            .or_default()
1042                            .insert(project_path.clone());
1043                    }
1044                    let timestamp = entry.timestamp;
1045                    match history.entry(project_path) {
1046                        hash_map::Entry::Occupied(mut entry) => {
1047                            let (_, old_timestamp) = entry.get();
1048                            if &timestamp > old_timestamp {
1049                                entry.insert((fs_path, timestamp));
1050                            }
1051                        }
1052                        hash_map::Entry::Vacant(entry) => {
1053                            entry.insert((fs_path, timestamp));
1054                        }
1055                    }
1056                });
1057        }
1058
1059        history
1060            .into_iter()
1061            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
1062            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
1063            .rev()
1064            .filter(|(history_path, abs_path)| {
1065                let latest_project_path_opened = abs_path
1066                    .as_ref()
1067                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
1068                    .and_then(|project_paths| {
1069                        project_paths
1070                            .iter()
1071                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1072                    });
1073
1074                match latest_project_path_opened {
1075                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1076                    None => true,
1077                }
1078            })
1079            .take(limit.unwrap_or(usize::MAX))
1080            .collect()
1081    }
1082
1083    fn navigate_history(
1084        &mut self,
1085        pane: WeakView<Pane>,
1086        mode: NavigationMode,
1087        cx: &mut ViewContext<Workspace>,
1088    ) -> Task<Result<()>> {
1089        let to_load = if let Some(pane) = pane.upgrade() {
1090            pane.update(cx, |pane, cx| {
1091                pane.focus(cx);
1092                loop {
1093                    // Retrieve the weak item handle from the history.
1094                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1095
1096                    // If the item is still present in this pane, then activate it.
1097                    if let Some(index) = entry
1098                        .item
1099                        .upgrade()
1100                        .and_then(|v| pane.index_for_item(v.as_ref()))
1101                    {
1102                        let prev_active_item_index = pane.active_item_index();
1103                        pane.nav_history_mut().set_mode(mode);
1104                        pane.activate_item(index, true, true, cx);
1105                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1106
1107                        let mut navigated = prev_active_item_index != pane.active_item_index();
1108                        if let Some(data) = entry.data {
1109                            navigated |= pane.active_item()?.navigate(data, cx);
1110                        }
1111
1112                        if navigated {
1113                            break None;
1114                        }
1115                    }
1116                    // If the item is no longer present in this pane, then retrieve its
1117                    // project path in order to reopen it.
1118                    else {
1119                        break pane
1120                            .nav_history()
1121                            .path_for_item(entry.item.id())
1122                            .map(|(project_path, _)| (project_path, entry));
1123                    }
1124                }
1125            })
1126        } else {
1127            None
1128        };
1129
1130        if let Some((project_path, entry)) = to_load {
1131            // If the item was no longer present, then load it again from its previous path.
1132            let task = self.load_path(project_path, cx);
1133            cx.spawn(|workspace, mut cx| async move {
1134                let task = task.await;
1135                let mut navigated = false;
1136                if let Some((project_entry_id, build_item)) = task.log_err() {
1137                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1138                        pane.nav_history_mut().set_mode(mode);
1139                        pane.active_item().map(|p| p.item_id())
1140                    })?;
1141
1142                    pane.update(&mut cx, |pane, cx| {
1143                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1144                        navigated |= Some(item.item_id()) != prev_active_item_id;
1145                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1146                        if let Some(data) = entry.data {
1147                            navigated |= item.navigate(data, cx);
1148                        }
1149                    })?;
1150                }
1151
1152                if !navigated {
1153                    workspace
1154                        .update(&mut cx, |workspace, cx| {
1155                            Self::navigate_history(workspace, pane, mode, cx)
1156                        })?
1157                        .await?;
1158                }
1159
1160                Ok(())
1161            })
1162        } else {
1163            Task::ready(Ok(()))
1164        }
1165    }
1166
1167    pub fn go_back(
1168        &mut self,
1169        pane: WeakView<Pane>,
1170        cx: &mut ViewContext<Workspace>,
1171    ) -> Task<Result<()>> {
1172        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1173    }
1174
1175    pub fn go_forward(
1176        &mut self,
1177        pane: WeakView<Pane>,
1178        cx: &mut ViewContext<Workspace>,
1179    ) -> Task<Result<()>> {
1180        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1181    }
1182
1183    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1184        self.navigate_history(
1185            self.active_pane().downgrade(),
1186            NavigationMode::ReopeningClosedItem,
1187            cx,
1188        )
1189    }
1190
1191    pub fn client(&self) -> &Arc<Client> {
1192        &self.app_state.client
1193    }
1194
1195    pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
1196        self.titlebar_item = Some(item);
1197        cx.notify();
1198    }
1199
1200    pub fn titlebar_item(&self) -> Option<AnyView> {
1201        self.titlebar_item.clone()
1202    }
1203
1204    /// Call the given callback with a workspace whose project is local.
1205    ///
1206    /// If the given workspace has a local project, then it will be passed
1207    /// to the callback. Otherwise, a new empty window will be created.
1208    pub fn with_local_workspace<T, F>(
1209        &mut self,
1210        cx: &mut ViewContext<Self>,
1211        callback: F,
1212    ) -> Task<Result<T>>
1213    where
1214        T: 'static,
1215        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1216    {
1217        if self.project.read(cx).is_local() {
1218            Task::Ready(Some(Ok(callback(self, cx))))
1219        } else {
1220            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1221            cx.spawn(|_vh, mut cx| async move {
1222                let (workspace, _) = task.await?;
1223                workspace.update(&mut cx, callback)
1224            })
1225        }
1226    }
1227
1228    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1229        self.project.read(cx).worktrees()
1230    }
1231
1232    pub fn visible_worktrees<'a>(
1233        &self,
1234        cx: &'a AppContext,
1235    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1236        self.project.read(cx).visible_worktrees(cx)
1237    }
1238
1239    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1240        let futures = self
1241            .worktrees(cx)
1242            .filter_map(|worktree| worktree.read(cx).as_local())
1243            .map(|worktree| worktree.scan_complete())
1244            .collect::<Vec<_>>();
1245        async move {
1246            for future in futures {
1247                future.await;
1248            }
1249        }
1250    }
1251
1252    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1253        cx.defer(|cx| {
1254            cx.windows().iter().find(|window| {
1255                window
1256                    .update(cx, |_, window| {
1257                        if window.is_window_active() {
1258                            //This can only get called when the window's project connection has been lost
1259                            //so we don't need to prompt the user for anything and instead just close the window
1260                            window.remove_window();
1261                            true
1262                        } else {
1263                            false
1264                        }
1265                    })
1266                    .unwrap_or(false)
1267            });
1268        });
1269    }
1270
1271    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
1272        let window = cx.window_handle();
1273        let prepare = self.prepare_to_close(false, cx);
1274        cx.spawn(|_, mut cx| async move {
1275            if prepare.await? {
1276                window.update(&mut cx, |_, cx| {
1277                    cx.remove_window();
1278                })?;
1279            }
1280            anyhow::Ok(())
1281        })
1282        .detach_and_log_err(cx)
1283    }
1284
1285    pub fn prepare_to_close(
1286        &mut self,
1287        quitting: bool,
1288        cx: &mut ViewContext<Self>,
1289    ) -> Task<Result<bool>> {
1290        let active_call = self.active_call().cloned();
1291        let window = cx.window_handle();
1292
1293        cx.spawn(|this, mut cx| async move {
1294            let workspace_count = (*cx).update(|cx| {
1295                cx.windows()
1296                    .iter()
1297                    .filter(|window| window.downcast::<Workspace>().is_some())
1298                    .count()
1299            })?;
1300
1301            if let Some(active_call) = active_call {
1302                if !quitting
1303                    && workspace_count == 1
1304                    && active_call.read_with(&cx, |call, _| call.room().is_some())?
1305                {
1306                    let answer = window.update(&mut cx, |_, cx| {
1307                        cx.prompt(
1308                            PromptLevel::Warning,
1309                            "Do you want to leave the current call?",
1310                            None,
1311                            &["Close window and hang up", "Cancel"],
1312                        )
1313                    })?;
1314
1315                    if answer.await.log_err() == Some(1) {
1316                        return anyhow::Ok(false);
1317                    } else {
1318                        active_call
1319                            .update(&mut cx, |call, cx| call.hang_up(cx))?
1320                            .await
1321                            .log_err();
1322                    }
1323                }
1324            }
1325
1326            this.update(&mut cx, |this, cx| {
1327                this.save_all_internal(SaveIntent::Close, cx)
1328            })?
1329            .await
1330        })
1331    }
1332
1333    fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
1334        self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
1335            .detach_and_log_err(cx);
1336    }
1337
1338    fn send_keystrokes(&mut self, action: &SendKeystrokes, cx: &mut ViewContext<Self>) {
1339        let mut keystrokes: Vec<Keystroke> = action
1340            .0
1341            .split(' ')
1342            .flat_map(|k| Keystroke::parse(k).log_err())
1343            .collect();
1344        keystrokes.reverse();
1345
1346        self.dispatching_keystrokes
1347            .borrow_mut()
1348            .append(&mut keystrokes);
1349
1350        let keystrokes = self.dispatching_keystrokes.clone();
1351        cx.window_context()
1352            .spawn(|mut cx| async move {
1353                // limit to 100 keystrokes to avoid infinite recursion.
1354                for _ in 0..100 {
1355                    let Some(keystroke) = keystrokes.borrow_mut().pop() else {
1356                        return Ok(());
1357                    };
1358                    cx.update(|cx| {
1359                        let focused = cx.focused();
1360                        cx.dispatch_keystroke(keystroke.clone());
1361                        if cx.focused() != focused {
1362                            // dispatch_keystroke may cause the focus to change.
1363                            // draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
1364                            // And we need that to happen before the next keystroke to keep vim mode happy...
1365                            // (Note that the tests always do this implicitly, so you must manually test with something like:
1366                            //   "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
1367                            // )
1368                            cx.draw();
1369                        }
1370                    })?;
1371                }
1372                keystrokes.borrow_mut().clear();
1373                Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
1374            })
1375            .detach_and_log_err(cx);
1376    }
1377
1378    fn save_all_internal(
1379        &mut self,
1380        mut save_intent: SaveIntent,
1381        cx: &mut ViewContext<Self>,
1382    ) -> Task<Result<bool>> {
1383        if self.project.read(cx).is_disconnected() {
1384            return Task::ready(Ok(true));
1385        }
1386        let dirty_items = self
1387            .panes
1388            .iter()
1389            .flat_map(|pane| {
1390                pane.read(cx).items().filter_map(|item| {
1391                    if item.is_dirty(cx) {
1392                        Some((pane.downgrade(), item.boxed_clone()))
1393                    } else {
1394                        None
1395                    }
1396                })
1397            })
1398            .collect::<Vec<_>>();
1399
1400        let project = self.project.clone();
1401        cx.spawn(|workspace, mut cx| async move {
1402            // Override save mode and display "Save all files" prompt
1403            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1404                let answer = workspace.update(&mut cx, |_, cx| {
1405                    let (prompt, detail) = Pane::file_names_for_prompt(
1406                        &mut dirty_items.iter().map(|(_, handle)| handle),
1407                        dirty_items.len(),
1408                        cx,
1409                    );
1410                    cx.prompt(
1411                        PromptLevel::Warning,
1412                        &prompt,
1413                        Some(&detail),
1414                        &["Save all", "Discard all", "Cancel"],
1415                    )
1416                })?;
1417                match answer.await.log_err() {
1418                    Some(0) => save_intent = SaveIntent::SaveAll,
1419                    Some(1) => save_intent = SaveIntent::Skip,
1420                    _ => {}
1421                }
1422            }
1423            for (pane, item) in dirty_items {
1424                let (singleton, project_entry_ids) =
1425                    cx.update(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1426                if singleton || !project_entry_ids.is_empty() {
1427                    if let Some(ix) =
1428                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1429                    {
1430                        if !Pane::save_item(
1431                            project.clone(),
1432                            &pane,
1433                            ix,
1434                            &*item,
1435                            save_intent,
1436                            &mut cx,
1437                        )
1438                        .await?
1439                        {
1440                            return Ok(false);
1441                        }
1442                    }
1443                }
1444            }
1445            Ok(true)
1446        })
1447    }
1448
1449    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
1450        self.client()
1451            .telemetry()
1452            .report_app_event("open project".to_string());
1453        let paths = cx.prompt_for_paths(PathPromptOptions {
1454            files: true,
1455            directories: true,
1456            multiple: true,
1457        });
1458
1459        cx.spawn(|this, mut cx| async move {
1460            let Some(paths) = paths.await.log_err().flatten() else {
1461                return;
1462            };
1463
1464            if let Some(task) = this
1465                .update(&mut cx, |this, cx| {
1466                    this.open_workspace_for_paths(false, paths, cx)
1467                })
1468                .log_err()
1469            {
1470                task.await.log_err();
1471            }
1472        })
1473        .detach()
1474    }
1475
1476    pub fn open_workspace_for_paths(
1477        &mut self,
1478        replace_current_window: bool,
1479        paths: Vec<PathBuf>,
1480        cx: &mut ViewContext<Self>,
1481    ) -> Task<Result<()>> {
1482        let window = cx.window_handle().downcast::<Self>();
1483        let is_remote = self.project.read(cx).is_remote();
1484        let has_worktree = self.project.read(cx).worktrees().next().is_some();
1485        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1486
1487        let window_to_replace = if replace_current_window {
1488            window
1489        } else if is_remote || has_worktree || has_dirty_items {
1490            None
1491        } else {
1492            window
1493        };
1494        let app_state = self.app_state.clone();
1495
1496        cx.spawn(|_, mut cx| async move {
1497            cx.update(|cx| {
1498                open_paths(
1499                    &paths,
1500                    app_state,
1501                    OpenOptions {
1502                        replace_window: window_to_replace,
1503                        ..Default::default()
1504                    },
1505                    cx,
1506                )
1507            })?
1508            .await?;
1509            Ok(())
1510        })
1511    }
1512
1513    #[allow(clippy::type_complexity)]
1514    pub fn open_paths(
1515        &mut self,
1516        mut abs_paths: Vec<PathBuf>,
1517        visible: OpenVisible,
1518        pane: Option<WeakView<Pane>>,
1519        cx: &mut ViewContext<Self>,
1520    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1521        log::info!("open paths {abs_paths:?}");
1522
1523        let fs = self.app_state.fs.clone();
1524
1525        // Sort the paths to ensure we add worktrees for parents before their children.
1526        abs_paths.sort_unstable();
1527        cx.spawn(move |this, mut cx| async move {
1528            let mut tasks = Vec::with_capacity(abs_paths.len());
1529
1530            for abs_path in &abs_paths {
1531                let visible = match visible {
1532                    OpenVisible::All => Some(true),
1533                    OpenVisible::None => Some(false),
1534                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
1535                        Some(Some(metadata)) => Some(!metadata.is_dir),
1536                        Some(None) => Some(true),
1537                        None => None,
1538                    },
1539                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
1540                        Some(Some(metadata)) => Some(metadata.is_dir),
1541                        Some(None) => Some(false),
1542                        None => None,
1543                    },
1544                };
1545                let project_path = match visible {
1546                    Some(visible) => match this
1547                        .update(&mut cx, |this, cx| {
1548                            Workspace::project_path_for_path(
1549                                this.project.clone(),
1550                                abs_path,
1551                                visible,
1552                                cx,
1553                            )
1554                        })
1555                        .log_err()
1556                    {
1557                        Some(project_path) => project_path.await.log_err(),
1558                        None => None,
1559                    },
1560                    None => None,
1561                };
1562
1563                let this = this.clone();
1564                let abs_path = abs_path.clone();
1565                let fs = fs.clone();
1566                let pane = pane.clone();
1567                let task = cx.spawn(move |mut cx| async move {
1568                    let (worktree, project_path) = project_path?;
1569                    if fs.is_dir(&abs_path).await {
1570                        this.update(&mut cx, |workspace, cx| {
1571                            let worktree = worktree.read(cx);
1572                            let worktree_abs_path = worktree.abs_path();
1573                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
1574                                worktree.root_entry()
1575                            } else {
1576                                abs_path
1577                                    .strip_prefix(worktree_abs_path.as_ref())
1578                                    .ok()
1579                                    .and_then(|relative_path| {
1580                                        worktree.entry_for_path(relative_path)
1581                                    })
1582                            }
1583                            .map(|entry| entry.id);
1584                            if let Some(entry_id) = entry_id {
1585                                workspace.project.update(cx, |_, cx| {
1586                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1587                                })
1588                            }
1589                        })
1590                        .log_err()?;
1591                        None
1592                    } else {
1593                        Some(
1594                            this.update(&mut cx, |this, cx| {
1595                                this.open_path(project_path, pane, true, cx)
1596                            })
1597                            .log_err()?
1598                            .await,
1599                        )
1600                    }
1601                });
1602                tasks.push(task);
1603            }
1604
1605            futures::future::join_all(tasks).await
1606        })
1607    }
1608
1609    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1610        let paths = cx.prompt_for_paths(PathPromptOptions {
1611            files: false,
1612            directories: true,
1613            multiple: true,
1614        });
1615        cx.spawn(|this, mut cx| async move {
1616            if let Some(paths) = paths.await.log_err().flatten() {
1617                let results = this
1618                    .update(&mut cx, |this, cx| {
1619                        this.open_paths(paths, OpenVisible::All, None, cx)
1620                    })?
1621                    .await;
1622                for result in results.into_iter().flatten() {
1623                    result.log_err();
1624                }
1625            }
1626            anyhow::Ok(())
1627        })
1628        .detach_and_log_err(cx);
1629    }
1630
1631    fn project_path_for_path(
1632        project: Model<Project>,
1633        abs_path: &Path,
1634        visible: bool,
1635        cx: &mut AppContext,
1636    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1637        let entry = project.update(cx, |project, cx| {
1638            project.find_or_create_local_worktree(abs_path, visible, cx)
1639        });
1640        cx.spawn(|mut cx| async move {
1641            let (worktree, path) = entry.await?;
1642            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1643            Ok((
1644                worktree,
1645                ProjectPath {
1646                    worktree_id,
1647                    path: path.into(),
1648                },
1649            ))
1650        })
1651    }
1652
1653    pub fn items<'a>(
1654        &'a self,
1655        cx: &'a AppContext,
1656    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1657        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1658    }
1659
1660    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1661        self.items_of_type(cx).max_by_key(|item| item.item_id())
1662    }
1663
1664    pub fn items_of_type<'a, T: Item>(
1665        &'a self,
1666        cx: &'a AppContext,
1667    ) -> impl 'a + Iterator<Item = View<T>> {
1668        self.panes
1669            .iter()
1670            .flat_map(|pane| pane.read(cx).items_of_type())
1671    }
1672
1673    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1674        self.active_pane().read(cx).active_item()
1675    }
1676
1677    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1678        let item = self.active_item(cx)?;
1679        item.to_any().downcast::<I>().ok()
1680    }
1681
1682    fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
1683        self.active_item(cx).and_then(|item| item.project_path(cx))
1684    }
1685
1686    pub fn save_active_item(
1687        &mut self,
1688        save_intent: SaveIntent,
1689        cx: &mut WindowContext,
1690    ) -> Task<Result<()>> {
1691        let project = self.project.clone();
1692        let pane = self.active_pane();
1693        let item_ix = pane.read(cx).active_item_index();
1694        let item = pane.read(cx).active_item();
1695        let pane = pane.downgrade();
1696
1697        cx.spawn(|mut cx| async move {
1698            if let Some(item) = item {
1699                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1700                    .await
1701                    .map(|_| ())
1702            } else {
1703                Ok(())
1704            }
1705        })
1706    }
1707
1708    pub fn close_inactive_items_and_panes(
1709        &mut self,
1710        action: &CloseInactiveTabsAndPanes,
1711        cx: &mut ViewContext<Self>,
1712    ) {
1713        if let Some(task) =
1714            self.close_all_internal(true, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1715        {
1716            task.detach_and_log_err(cx)
1717        }
1718    }
1719
1720    pub fn close_all_items_and_panes(
1721        &mut self,
1722        action: &CloseAllItemsAndPanes,
1723        cx: &mut ViewContext<Self>,
1724    ) {
1725        if let Some(task) =
1726            self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1727        {
1728            task.detach_and_log_err(cx)
1729        }
1730    }
1731
1732    fn close_all_internal(
1733        &mut self,
1734        retain_active_pane: bool,
1735        save_intent: SaveIntent,
1736        cx: &mut ViewContext<Self>,
1737    ) -> Option<Task<Result<()>>> {
1738        let current_pane = self.active_pane();
1739
1740        let mut tasks = Vec::new();
1741
1742        if retain_active_pane {
1743            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1744                pane.close_inactive_items(&CloseInactiveItems { save_intent: None }, cx)
1745            }) {
1746                tasks.push(current_pane_close);
1747            };
1748        }
1749
1750        for pane in self.panes() {
1751            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1752                continue;
1753            }
1754
1755            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1756                pane.close_all_items(
1757                    &CloseAllItems {
1758                        save_intent: Some(save_intent),
1759                    },
1760                    cx,
1761                )
1762            }) {
1763                tasks.push(close_pane_items)
1764            }
1765        }
1766
1767        if tasks.is_empty() {
1768            None
1769        } else {
1770            Some(cx.spawn(|_, _| async move {
1771                for task in tasks {
1772                    task.await?
1773                }
1774                Ok(())
1775            }))
1776        }
1777    }
1778
1779    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1780        let dock = match dock_side {
1781            DockPosition::Left => &self.left_dock,
1782            DockPosition::Bottom => &self.bottom_dock,
1783            DockPosition::Right => &self.right_dock,
1784        };
1785        let mut focus_center = false;
1786        let mut reveal_dock = false;
1787        dock.update(cx, |dock, cx| {
1788            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1789            let was_visible = dock.is_open() && !other_is_zoomed;
1790            dock.set_open(!was_visible, cx);
1791
1792            if let Some(active_panel) = dock.active_panel() {
1793                if was_visible {
1794                    if active_panel.focus_handle(cx).contains_focused(cx) {
1795                        focus_center = true;
1796                    }
1797                } else {
1798                    let focus_handle = &active_panel.focus_handle(cx);
1799                    cx.focus(focus_handle);
1800                    reveal_dock = true;
1801                }
1802            }
1803        });
1804
1805        if reveal_dock {
1806            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1807        }
1808
1809        if focus_center {
1810            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1811        }
1812
1813        cx.notify();
1814        self.serialize_workspace(cx).detach();
1815    }
1816
1817    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1818        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1819
1820        for dock in docks {
1821            dock.update(cx, |dock, cx| {
1822                dock.set_open(false, cx);
1823            });
1824        }
1825
1826        cx.focus_self();
1827        cx.notify();
1828        self.serialize_workspace(cx).detach();
1829    }
1830
1831    /// Transfer focus to the panel of the given type.
1832    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1833        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1834        panel.to_any().downcast().ok()
1835    }
1836
1837    /// Focus the panel of the given type if it isn't already focused. If it is
1838    /// already focused, then transfer focus back to the workspace center.
1839    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1840        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
1841            !panel.focus_handle(cx).contains_focused(cx)
1842        });
1843    }
1844
1845    /// Focus or unfocus the given panel type, depending on the given callback.
1846    fn focus_or_unfocus_panel<T: Panel>(
1847        &mut self,
1848        cx: &mut ViewContext<Self>,
1849        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1850    ) -> Option<Arc<dyn PanelHandle>> {
1851        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1852            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1853                let mut focus_center = false;
1854                let panel = dock.update(cx, |dock, cx| {
1855                    dock.activate_panel(panel_index, cx);
1856
1857                    let panel = dock.active_panel().cloned();
1858                    if let Some(panel) = panel.as_ref() {
1859                        if should_focus(&**panel, cx) {
1860                            dock.set_open(true, cx);
1861                            panel.focus_handle(cx).focus(cx);
1862                        } else {
1863                            focus_center = true;
1864                        }
1865                    }
1866                    panel
1867                });
1868
1869                if focus_center {
1870                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1871                }
1872
1873                self.serialize_workspace(cx).detach();
1874                cx.notify();
1875                return panel;
1876            }
1877        }
1878        None
1879    }
1880
1881    /// Open the panel of the given type
1882    pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1883        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1884            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1885                dock.update(cx, |dock, cx| {
1886                    dock.activate_panel(panel_index, cx);
1887                    dock.set_open(true, cx);
1888                });
1889            }
1890        }
1891    }
1892
1893    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1894        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1895            let dock = dock.read(cx);
1896            if let Some(panel) = dock.panel::<T>() {
1897                return Some(panel);
1898            }
1899        }
1900        None
1901    }
1902
1903    fn dismiss_zoomed_items_to_reveal(
1904        &mut self,
1905        dock_to_reveal: Option<DockPosition>,
1906        cx: &mut ViewContext<Self>,
1907    ) {
1908        // If a center pane is zoomed, unzoom it.
1909        for pane in &self.panes {
1910            if pane != &self.active_pane || dock_to_reveal.is_some() {
1911                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1912            }
1913        }
1914
1915        // If another dock is zoomed, hide it.
1916        let mut focus_center = false;
1917        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1918            dock.update(cx, |dock, cx| {
1919                if Some(dock.position()) != dock_to_reveal {
1920                    if let Some(panel) = dock.active_panel() {
1921                        if panel.is_zoomed(cx) {
1922                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
1923                            dock.set_open(false, cx);
1924                        }
1925                    }
1926                }
1927            });
1928        }
1929
1930        if focus_center {
1931            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1932        }
1933
1934        if self.zoomed_position != dock_to_reveal {
1935            self.zoomed = None;
1936            self.zoomed_position = None;
1937        }
1938
1939        cx.notify();
1940    }
1941
1942    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
1943        let pane = cx.new_view(|cx| {
1944            Pane::new(
1945                self.weak_handle(),
1946                self.project.clone(),
1947                self.pane_history_timestamp.clone(),
1948                None,
1949                NewFile.boxed_clone(),
1950                cx,
1951            )
1952        });
1953        cx.subscribe(&pane, Self::handle_pane_event).detach();
1954        self.panes.push(pane.clone());
1955        cx.focus_view(&pane);
1956        cx.emit(Event::PaneAdded(pane.clone()));
1957        pane
1958    }
1959
1960    pub fn add_item_to_center(
1961        &mut self,
1962        item: Box<dyn ItemHandle>,
1963        cx: &mut ViewContext<Self>,
1964    ) -> bool {
1965        if let Some(center_pane) = self.last_active_center_pane.clone() {
1966            if let Some(center_pane) = center_pane.upgrade() {
1967                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1968                true
1969            } else {
1970                false
1971            }
1972        } else {
1973            false
1974        }
1975    }
1976
1977    pub fn add_item_to_active_pane(&mut self, item: Box<dyn ItemHandle>, cx: &mut WindowContext) {
1978        self.add_item(self.active_pane.clone(), item, cx)
1979    }
1980
1981    pub fn add_item(
1982        &mut self,
1983        pane: View<Pane>,
1984        item: Box<dyn ItemHandle>,
1985        cx: &mut WindowContext,
1986    ) {
1987        if let Some(text) = item.telemetry_event_text(cx) {
1988            self.client()
1989                .telemetry()
1990                .report_app_event(format!("{}: open", text));
1991        }
1992
1993        pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1994    }
1995
1996    pub fn split_item(
1997        &mut self,
1998        split_direction: SplitDirection,
1999        item: Box<dyn ItemHandle>,
2000        cx: &mut ViewContext<Self>,
2001    ) {
2002        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
2003        self.add_item(new_pane, item, cx);
2004    }
2005
2006    pub fn open_abs_path(
2007        &mut self,
2008        abs_path: PathBuf,
2009        visible: bool,
2010        cx: &mut ViewContext<Self>,
2011    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2012        cx.spawn(|workspace, mut cx| async move {
2013            let open_paths_task_result = workspace
2014                .update(&mut cx, |workspace, cx| {
2015                    workspace.open_paths(
2016                        vec![abs_path.clone()],
2017                        if visible {
2018                            OpenVisible::All
2019                        } else {
2020                            OpenVisible::None
2021                        },
2022                        None,
2023                        cx,
2024                    )
2025                })
2026                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
2027                .await;
2028            anyhow::ensure!(
2029                open_paths_task_result.len() == 1,
2030                "open abs path {abs_path:?} task returned incorrect number of results"
2031            );
2032            match open_paths_task_result
2033                .into_iter()
2034                .next()
2035                .expect("ensured single task result")
2036            {
2037                Some(open_result) => {
2038                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
2039                }
2040                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
2041            }
2042        })
2043    }
2044
2045    pub fn split_abs_path(
2046        &mut self,
2047        abs_path: PathBuf,
2048        visible: bool,
2049        cx: &mut ViewContext<Self>,
2050    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2051        let project_path_task =
2052            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2053        cx.spawn(|this, mut cx| async move {
2054            let (_, path) = project_path_task.await?;
2055            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2056                .await
2057        })
2058    }
2059
2060    pub fn open_path(
2061        &mut self,
2062        path: impl Into<ProjectPath>,
2063        pane: Option<WeakView<Pane>>,
2064        focus_item: bool,
2065        cx: &mut WindowContext,
2066    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2067        let pane = pane.unwrap_or_else(|| {
2068            self.last_active_center_pane.clone().unwrap_or_else(|| {
2069                self.panes
2070                    .first()
2071                    .expect("There must be an active pane")
2072                    .downgrade()
2073            })
2074        });
2075
2076        let task = self.load_path(path.into(), cx);
2077        cx.spawn(move |mut cx| async move {
2078            let (project_entry_id, build_item) = task.await?;
2079            pane.update(&mut cx, |pane, cx| {
2080                pane.open_item(project_entry_id, focus_item, cx, build_item)
2081            })
2082        })
2083    }
2084
2085    pub fn split_path(
2086        &mut self,
2087        path: impl Into<ProjectPath>,
2088        cx: &mut ViewContext<Self>,
2089    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2090        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2091            self.panes
2092                .first()
2093                .expect("There must be an active pane")
2094                .downgrade()
2095        });
2096
2097        if let Member::Pane(center_pane) = &self.center.root {
2098            if center_pane.read(cx).items_len() == 0 {
2099                return self.open_path(path, Some(pane), true, cx);
2100            }
2101        }
2102
2103        let task = self.load_path(path.into(), cx);
2104        cx.spawn(|this, mut cx| async move {
2105            let (project_entry_id, build_item) = task.await?;
2106            this.update(&mut cx, move |this, cx| -> Option<_> {
2107                let pane = pane.upgrade()?;
2108                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2109                new_pane.update(cx, |new_pane, cx| {
2110                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
2111                })
2112            })
2113            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2114        })
2115    }
2116
2117    fn load_path(
2118        &mut self,
2119        path: ProjectPath,
2120        cx: &mut WindowContext,
2121    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
2122        let project = self.project().clone();
2123        let project_item_builders = cx.default_global::<ProjectItemOpeners>().clone();
2124        let Some(open_project_item) = project_item_builders
2125            .iter()
2126            .rev()
2127            .find_map(|open_project_item| open_project_item(&project, &path, cx))
2128        else {
2129            return Task::ready(Err(anyhow!("cannot open file {:?}", path.path)));
2130        };
2131        open_project_item
2132    }
2133
2134    pub fn open_project_item<T>(
2135        &mut self,
2136        pane: View<Pane>,
2137        project_item: Model<T::Item>,
2138        cx: &mut ViewContext<Self>,
2139    ) -> View<T>
2140    where
2141        T: ProjectItem,
2142    {
2143        use project::Item as _;
2144
2145        let entry_id = project_item.read(cx).entry_id(cx);
2146        if let Some(item) = entry_id
2147            .and_then(|entry_id| pane.read(cx).item_for_entry(entry_id, cx))
2148            .and_then(|item| item.downcast())
2149        {
2150            self.activate_item(&item, cx);
2151            return item;
2152        }
2153
2154        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2155        self.add_item(pane, Box::new(item.clone()), cx);
2156        item
2157    }
2158
2159    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2160        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2161            self.active_pane.update(cx, |pane, cx| {
2162                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2163            });
2164        }
2165    }
2166
2167    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
2168        let result = self.panes.iter().find_map(|pane| {
2169            pane.read(cx)
2170                .index_for_item(item)
2171                .map(|ix| (pane.clone(), ix))
2172        });
2173        if let Some((pane, ix)) = result {
2174            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2175            true
2176        } else {
2177            false
2178        }
2179    }
2180
2181    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2182        let panes = self.center.panes();
2183        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2184            cx.focus_view(&pane);
2185        } else {
2186            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2187        }
2188    }
2189
2190    pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
2191        let panes = self.center.panes();
2192        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2193            let next_ix = (ix + 1) % panes.len();
2194            let next_pane = panes[next_ix].clone();
2195            cx.focus_view(&next_pane);
2196        }
2197    }
2198
2199    pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
2200        let panes = self.center.panes();
2201        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2202            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2203            let prev_pane = panes[prev_ix].clone();
2204            cx.focus_view(&prev_pane);
2205        }
2206    }
2207
2208    pub fn activate_pane_in_direction(
2209        &mut self,
2210        direction: SplitDirection,
2211        cx: &mut WindowContext,
2212    ) {
2213        use ActivateInDirectionTarget as Target;
2214        enum Origin {
2215            LeftDock,
2216            RightDock,
2217            BottomDock,
2218            Center,
2219        }
2220
2221        let origin: Origin = [
2222            (&self.left_dock, Origin::LeftDock),
2223            (&self.right_dock, Origin::RightDock),
2224            (&self.bottom_dock, Origin::BottomDock),
2225        ]
2226        .into_iter()
2227        .find_map(|(dock, origin)| {
2228            if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() {
2229                Some(origin)
2230            } else {
2231                None
2232            }
2233        })
2234        .unwrap_or(Origin::Center);
2235
2236        let get_last_active_pane = || {
2237            self.last_active_center_pane.as_ref().and_then(|p| {
2238                let p = p.upgrade()?;
2239                (p.read(cx).items_len() != 0).then_some(p)
2240            })
2241        };
2242
2243        let try_dock =
2244            |dock: &View<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
2245
2246        let target = match (origin, direction) {
2247            // We're in the center, so we first try to go to a different pane,
2248            // otherwise try to go to a dock.
2249            (Origin::Center, direction) => {
2250                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2251                    Some(Target::Pane(pane))
2252                } else {
2253                    match direction {
2254                        SplitDirection::Up => None,
2255                        SplitDirection::Down => try_dock(&self.bottom_dock),
2256                        SplitDirection::Left => try_dock(&self.left_dock),
2257                        SplitDirection::Right => try_dock(&self.right_dock),
2258                    }
2259                }
2260            }
2261
2262            (Origin::LeftDock, SplitDirection::Right) => {
2263                if let Some(last_active_pane) = get_last_active_pane() {
2264                    Some(Target::Pane(last_active_pane))
2265                } else {
2266                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
2267                }
2268            }
2269
2270            (Origin::LeftDock, SplitDirection::Down)
2271            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
2272
2273            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
2274            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
2275            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
2276
2277            (Origin::RightDock, SplitDirection::Left) => {
2278                if let Some(last_active_pane) = get_last_active_pane() {
2279                    Some(Target::Pane(last_active_pane))
2280                } else {
2281                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
2282                }
2283            }
2284
2285            _ => None,
2286        };
2287
2288        match target {
2289            Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane),
2290            Some(ActivateInDirectionTarget::Dock(dock)) => {
2291                if let Some(panel) = dock.read(cx).active_panel() {
2292                    panel.focus_handle(cx).focus(cx);
2293                } else {
2294                    log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.read(cx).position());
2295                }
2296            }
2297            None => {}
2298        }
2299    }
2300
2301    fn find_pane_in_direction(
2302        &mut self,
2303        direction: SplitDirection,
2304        cx: &WindowContext,
2305    ) -> Option<View<Pane>> {
2306        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2307            return None;
2308        };
2309        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2310        let center = match cursor {
2311            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2312            _ => bounding_box.center(),
2313        };
2314
2315        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2316
2317        let target = match direction {
2318            SplitDirection::Left => {
2319                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2320            }
2321            SplitDirection::Right => {
2322                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2323            }
2324            SplitDirection::Up => {
2325                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2326            }
2327            SplitDirection::Down => {
2328                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2329            }
2330        };
2331        self.center.pane_at_pixel_position(target).cloned()
2332    }
2333
2334    pub fn swap_pane_in_direction(
2335        &mut self,
2336        direction: SplitDirection,
2337        cx: &mut ViewContext<Self>,
2338    ) {
2339        if let Some(to) = self
2340            .find_pane_in_direction(direction, cx)
2341            .map(|pane| pane.clone())
2342        {
2343            self.center.swap(&self.active_pane.clone(), &to);
2344            cx.notify();
2345        }
2346    }
2347
2348    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2349        if self.active_pane != pane {
2350            self.active_pane = pane.clone();
2351            self.status_bar.update(cx, |status_bar, cx| {
2352                status_bar.set_active_pane(&self.active_pane, cx);
2353            });
2354            self.active_item_path_changed(cx);
2355            self.last_active_center_pane = Some(pane.downgrade());
2356        }
2357
2358        self.dismiss_zoomed_items_to_reveal(None, cx);
2359        if pane.read(cx).is_zoomed() {
2360            self.zoomed = Some(pane.downgrade().into());
2361        } else {
2362            self.zoomed = None;
2363        }
2364        self.zoomed_position = None;
2365        self.update_active_view_for_followers(cx);
2366
2367        cx.notify();
2368    }
2369
2370    fn handle_pane_event(
2371        &mut self,
2372        pane: View<Pane>,
2373        event: &pane::Event,
2374        cx: &mut ViewContext<Self>,
2375    ) {
2376        match event {
2377            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2378            pane::Event::Split(direction) => {
2379                self.split_and_clone(pane, *direction, cx);
2380            }
2381            pane::Event::Remove => self.remove_pane(pane, cx),
2382            pane::Event::ActivateItem { local } => {
2383                if *local {
2384                    self.unfollow(&pane, cx);
2385                }
2386                if &pane == self.active_pane() {
2387                    self.active_item_path_changed(cx);
2388                    self.update_active_view_for_followers(cx);
2389                }
2390            }
2391            pane::Event::ChangeItemTitle => {
2392                if pane == self.active_pane {
2393                    self.active_item_path_changed(cx);
2394                }
2395                self.update_window_edited(cx);
2396            }
2397            pane::Event::RemoveItem { item_id } => {
2398                self.update_window_edited(cx);
2399                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2400                    if entry.get().entity_id() == pane.entity_id() {
2401                        entry.remove();
2402                    }
2403                }
2404            }
2405            pane::Event::Focus => {
2406                self.handle_pane_focused(pane.clone(), cx);
2407            }
2408            pane::Event::ZoomIn => {
2409                if pane == self.active_pane {
2410                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2411                    if pane.read(cx).has_focus(cx) {
2412                        self.zoomed = Some(pane.downgrade().into());
2413                        self.zoomed_position = None;
2414                    }
2415                    cx.notify();
2416                }
2417            }
2418            pane::Event::ZoomOut => {
2419                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2420                if self.zoomed_position.is_none() {
2421                    self.zoomed = None;
2422                }
2423                cx.notify();
2424            }
2425        }
2426
2427        self.serialize_workspace(cx).detach();
2428    }
2429
2430    pub fn split_pane(
2431        &mut self,
2432        pane_to_split: View<Pane>,
2433        split_direction: SplitDirection,
2434        cx: &mut ViewContext<Self>,
2435    ) -> View<Pane> {
2436        let new_pane = self.add_pane(cx);
2437        self.center
2438            .split(&pane_to_split, &new_pane, split_direction)
2439            .unwrap();
2440        cx.notify();
2441        new_pane
2442    }
2443
2444    pub fn split_and_clone(
2445        &mut self,
2446        pane: View<Pane>,
2447        direction: SplitDirection,
2448        cx: &mut ViewContext<Self>,
2449    ) -> Option<View<Pane>> {
2450        let item = pane.read(cx).active_item()?;
2451        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2452            let new_pane = self.add_pane(cx);
2453            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2454            self.center.split(&pane, &new_pane, direction).unwrap();
2455            Some(new_pane)
2456        } else {
2457            None
2458        };
2459        cx.notify();
2460        maybe_pane_handle
2461    }
2462
2463    pub fn split_pane_with_item(
2464        &mut self,
2465        pane_to_split: WeakView<Pane>,
2466        split_direction: SplitDirection,
2467        from: WeakView<Pane>,
2468        item_id_to_move: EntityId,
2469        cx: &mut ViewContext<Self>,
2470    ) {
2471        let Some(pane_to_split) = pane_to_split.upgrade() else {
2472            return;
2473        };
2474        let Some(from) = from.upgrade() else {
2475            return;
2476        };
2477
2478        let new_pane = self.add_pane(cx);
2479        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2480        self.center
2481            .split(&pane_to_split, &new_pane, split_direction)
2482            .unwrap();
2483        cx.notify();
2484    }
2485
2486    pub fn split_pane_with_project_entry(
2487        &mut self,
2488        pane_to_split: WeakView<Pane>,
2489        split_direction: SplitDirection,
2490        project_entry: ProjectEntryId,
2491        cx: &mut ViewContext<Self>,
2492    ) -> Option<Task<Result<()>>> {
2493        let pane_to_split = pane_to_split.upgrade()?;
2494        let new_pane = self.add_pane(cx);
2495        self.center
2496            .split(&pane_to_split, &new_pane, split_direction)
2497            .unwrap();
2498
2499        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2500        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2501        Some(cx.foreground_executor().spawn(async move {
2502            task.await?;
2503            Ok(())
2504        }))
2505    }
2506
2507    pub fn move_item(
2508        &mut self,
2509        source: View<Pane>,
2510        destination: View<Pane>,
2511        item_id_to_move: EntityId,
2512        destination_index: usize,
2513        cx: &mut ViewContext<Self>,
2514    ) {
2515        let Some((item_ix, item_handle)) = source
2516            .read(cx)
2517            .items()
2518            .enumerate()
2519            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
2520        else {
2521            // Tab was closed during drag
2522            return;
2523        };
2524
2525        let item_handle = item_handle.clone();
2526
2527        if source != destination {
2528            // Close item from previous pane
2529            source.update(cx, |source, cx| {
2530                source.remove_item(item_ix, false, cx);
2531            });
2532        }
2533
2534        // This automatically removes duplicate items in the pane
2535        destination.update(cx, |destination, cx| {
2536            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2537            destination.focus(cx)
2538        });
2539    }
2540
2541    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2542        if self.center.remove(&pane).unwrap() {
2543            self.force_remove_pane(&pane, cx);
2544            self.unfollow(&pane, cx);
2545            self.last_leaders_by_pane.remove(&pane.downgrade());
2546            for removed_item in pane.read(cx).items() {
2547                self.panes_by_item.remove(&removed_item.item_id());
2548            }
2549
2550            cx.notify();
2551        } else {
2552            self.active_item_path_changed(cx);
2553        }
2554    }
2555
2556    pub fn panes(&self) -> &[View<Pane>] {
2557        &self.panes
2558    }
2559
2560    pub fn active_pane(&self) -> &View<Pane> {
2561        &self.active_pane
2562    }
2563
2564    pub fn adjacent_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
2565        self.find_pane_in_direction(SplitDirection::Right, cx)
2566            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
2567            .unwrap_or_else(|| self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx))
2568            .clone()
2569    }
2570
2571    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2572        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2573        weak_pane.upgrade()
2574    }
2575
2576    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2577        self.follower_states.retain(|_, state| {
2578            if state.leader_id == peer_id {
2579                for item in state.items_by_leader_view_id.values() {
2580                    item.set_leader_peer_id(None, cx);
2581                }
2582                false
2583            } else {
2584                true
2585            }
2586        });
2587        cx.notify();
2588    }
2589
2590    pub fn start_following(
2591        &mut self,
2592        leader_id: PeerId,
2593        cx: &mut ViewContext<Self>,
2594    ) -> Option<Task<Result<()>>> {
2595        let pane = self.active_pane().clone();
2596
2597        self.last_leaders_by_pane
2598            .insert(pane.downgrade(), leader_id);
2599        self.unfollow(&pane, cx);
2600        self.follower_states.insert(
2601            pane.clone(),
2602            FollowerState {
2603                leader_id,
2604                active_view_id: None,
2605                items_by_leader_view_id: Default::default(),
2606            },
2607        );
2608        cx.notify();
2609
2610        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2611        let project_id = self.project.read(cx).remote_id();
2612        let request = self.app_state.client.request(proto::Follow {
2613            room_id,
2614            project_id,
2615            leader_id: Some(leader_id),
2616        });
2617
2618        Some(cx.spawn(|this, mut cx| async move {
2619            let response = request.await?;
2620            this.update(&mut cx, |this, _| {
2621                let state = this
2622                    .follower_states
2623                    .get_mut(&pane)
2624                    .ok_or_else(|| anyhow!("following interrupted"))?;
2625                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2626                    Some(ViewId::from_proto(active_view_id)?)
2627                } else {
2628                    None
2629                };
2630                Ok::<_, anyhow::Error>(())
2631            })??;
2632            if let Some(view) = response.active_view {
2633                Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, &mut cx)
2634                    .await?;
2635            }
2636            Self::add_views_from_leader(
2637                this.clone(),
2638                leader_id,
2639                vec![pane],
2640                response.views,
2641                &mut cx,
2642            )
2643            .await?;
2644            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2645            Ok(())
2646        }))
2647    }
2648
2649    pub fn follow_next_collaborator(
2650        &mut self,
2651        _: &FollowNextCollaborator,
2652        cx: &mut ViewContext<Self>,
2653    ) {
2654        let collaborators = self.project.read(cx).collaborators();
2655        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2656            let mut collaborators = collaborators.keys().copied();
2657            for peer_id in collaborators.by_ref() {
2658                if peer_id == leader_id {
2659                    break;
2660                }
2661            }
2662            collaborators.next()
2663        } else if let Some(last_leader_id) =
2664            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2665        {
2666            if collaborators.contains_key(last_leader_id) {
2667                Some(*last_leader_id)
2668            } else {
2669                None
2670            }
2671        } else {
2672            None
2673        };
2674
2675        let pane = self.active_pane.clone();
2676        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2677        else {
2678            return;
2679        };
2680        if Some(leader_id) == self.unfollow(&pane, cx) {
2681            return;
2682        }
2683        if let Some(task) = self.start_following(leader_id, cx) {
2684            task.detach_and_log_err(cx)
2685        }
2686    }
2687
2688    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
2689        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
2690            return;
2691        };
2692        let room = room.read(cx);
2693        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2694            return;
2695        };
2696
2697        let project = self.project.read(cx);
2698
2699        let other_project_id = match remote_participant.location {
2700            call::ParticipantLocation::External => None,
2701            call::ParticipantLocation::UnsharedProject => None,
2702            call::ParticipantLocation::SharedProject { project_id } => {
2703                if Some(project_id) == project.remote_id() {
2704                    None
2705                } else {
2706                    Some(project_id)
2707                }
2708            }
2709        };
2710
2711        // if they are active in another project, follow there.
2712        if let Some(project_id) = other_project_id {
2713            let app_state = self.app_state.clone();
2714            crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
2715                .detach_and_log_err(cx);
2716        }
2717
2718        // if you're already following, find the right pane and focus it.
2719        for (pane, state) in &self.follower_states {
2720            if leader_id == state.leader_id {
2721                cx.focus_view(pane);
2722                return;
2723            }
2724        }
2725
2726        // Otherwise, follow.
2727        if let Some(task) = self.start_following(leader_id, cx) {
2728            task.detach_and_log_err(cx)
2729        }
2730    }
2731
2732    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2733        let state = self.follower_states.remove(pane)?;
2734        let leader_id = state.leader_id;
2735        for (_, item) in state.items_by_leader_view_id {
2736            item.set_leader_peer_id(None, cx);
2737        }
2738
2739        if self
2740            .follower_states
2741            .values()
2742            .all(|state| state.leader_id != leader_id)
2743        {
2744            let project_id = self.project.read(cx).remote_id();
2745            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2746            self.app_state
2747                .client
2748                .send(proto::Unfollow {
2749                    room_id,
2750                    project_id,
2751                    leader_id: Some(leader_id),
2752                })
2753                .log_err();
2754        }
2755
2756        cx.notify();
2757        Some(leader_id)
2758    }
2759
2760    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2761        self.follower_states
2762            .values()
2763            .any(|state| state.leader_id == peer_id)
2764    }
2765
2766    fn active_item_path_changed(&mut self, cx: &mut WindowContext) {
2767        let active_entry = self.active_project_path(cx);
2768        self.project
2769            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2770        self.update_window_title(cx);
2771    }
2772
2773    fn update_window_title(&mut self, cx: &mut WindowContext) {
2774        let project = self.project().read(cx);
2775        let mut title = String::new();
2776
2777        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2778            let filename = path
2779                .path
2780                .file_name()
2781                .map(|s| s.to_string_lossy())
2782                .or_else(|| {
2783                    Some(Cow::Borrowed(
2784                        project
2785                            .worktree_for_id(path.worktree_id, cx)?
2786                            .read(cx)
2787                            .root_name(),
2788                    ))
2789                });
2790
2791            if let Some(filename) = filename {
2792                title.push_str(filename.as_ref());
2793                title.push_str("");
2794            }
2795        }
2796
2797        for (i, name) in project.worktree_root_names(cx).enumerate() {
2798            if i > 0 {
2799                title.push_str(", ");
2800            }
2801            title.push_str(name);
2802        }
2803
2804        if title.is_empty() {
2805            title = "empty project".to_string();
2806        }
2807
2808        if project.is_remote() {
2809            title.push_str("");
2810        } else if project.is_shared() {
2811            title.push_str("");
2812        }
2813
2814        cx.set_window_title(&title);
2815    }
2816
2817    fn update_window_edited(&mut self, cx: &mut WindowContext) {
2818        let is_edited = !self.project.read(cx).is_disconnected()
2819            && self
2820                .items(cx)
2821                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2822        if is_edited != self.window_edited {
2823            self.window_edited = is_edited;
2824            cx.set_window_edited(self.window_edited)
2825        }
2826    }
2827
2828    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
2829        if self.notifications.is_empty() {
2830            None
2831        } else {
2832            Some(
2833                div()
2834                    .absolute()
2835                    .right_3()
2836                    .bottom_3()
2837                    .w_112()
2838                    .h_full()
2839                    .flex()
2840                    .flex_col()
2841                    .justify_end()
2842                    .gap_2()
2843                    .children(
2844                        self.notifications
2845                            .iter()
2846                            .map(|(_, _, notification)| notification.to_any()),
2847                    ),
2848            )
2849        }
2850    }
2851
2852    // RPC handlers
2853
2854    fn active_view_for_follower(
2855        &self,
2856        follower_project_id: Option<u64>,
2857        cx: &mut ViewContext<Self>,
2858    ) -> Option<proto::View> {
2859        let item = self.active_item(cx)?;
2860        let leader_id = self
2861            .pane_for(&*item)
2862            .and_then(|pane| self.leader_for_pane(&pane));
2863
2864        let item_handle = item.to_followable_item_handle(cx)?;
2865        let id = item_handle.remote_id(&self.app_state.client, cx)?;
2866        let variant = item_handle.to_state_proto(cx)?;
2867
2868        if item_handle.is_project_item(cx)
2869            && (follower_project_id.is_none()
2870                || follower_project_id != self.project.read(cx).remote_id())
2871        {
2872            return None;
2873        }
2874
2875        Some(proto::View {
2876            id: Some(id.to_proto()),
2877            leader_id,
2878            variant: Some(variant),
2879        })
2880    }
2881
2882    fn handle_follow(
2883        &mut self,
2884        follower_project_id: Option<u64>,
2885        cx: &mut ViewContext<Self>,
2886    ) -> proto::FollowResponse {
2887        let client = &self.app_state.client;
2888        let project_id = self.project.read(cx).remote_id();
2889
2890        let active_view = self.active_view_for_follower(follower_project_id, cx);
2891        let active_view_id = active_view.as_ref().and_then(|view| view.id.clone());
2892
2893        cx.notify();
2894
2895        proto::FollowResponse {
2896            active_view,
2897            // TODO: once v0.124.0 is retired we can stop sending these
2898            active_view_id,
2899            views: self
2900                .panes()
2901                .iter()
2902                .flat_map(|pane| {
2903                    let leader_id = self.leader_for_pane(pane);
2904                    pane.read(cx).items().filter_map({
2905                        let cx = &cx;
2906                        move |item| {
2907                            let item = item.to_followable_item_handle(cx)?;
2908
2909                            // If the item belongs to a particular project, then it should
2910                            // only be included if this project is shared, and the follower
2911                            // is in the project.
2912                            //
2913                            // Some items, like channel notes, do not belong to a particular
2914                            // project, so they should be included regardless of whether the
2915                            // current project is shared, or what project the follower is in.
2916                            if item.is_project_item(cx)
2917                                && (project_id.is_none() || project_id != follower_project_id)
2918                            {
2919                                return None;
2920                            }
2921
2922                            let id = item.remote_id(client, cx)?.to_proto();
2923                            let variant = item.to_state_proto(cx)?;
2924                            Some(proto::View {
2925                                id: Some(id),
2926                                leader_id,
2927                                variant: Some(variant),
2928                            })
2929                        }
2930                    })
2931                })
2932                .collect(),
2933        }
2934    }
2935
2936    fn handle_update_followers(
2937        &mut self,
2938        leader_id: PeerId,
2939        message: proto::UpdateFollowers,
2940        _cx: &mut ViewContext<Self>,
2941    ) {
2942        self.leader_updates_tx
2943            .unbounded_send((leader_id, message))
2944            .ok();
2945    }
2946
2947    async fn process_leader_update(
2948        this: &WeakView<Self>,
2949        leader_id: PeerId,
2950        update: proto::UpdateFollowers,
2951        cx: &mut AsyncWindowContext,
2952    ) -> Result<()> {
2953        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2954            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2955                let panes_missing_view = this.update(cx, |this, _| {
2956                    let mut panes = vec![];
2957                    for (pane, state) in &mut this.follower_states {
2958                        if state.leader_id != leader_id {
2959                            continue;
2960                        }
2961
2962                        state.active_view_id =
2963                            if let Some(active_view_id) = update_active_view.id.clone() {
2964                                Some(ViewId::from_proto(active_view_id)?)
2965                            } else {
2966                                None
2967                            };
2968
2969                        if state.active_view_id.is_some_and(|view_id| {
2970                            !state.items_by_leader_view_id.contains_key(&view_id)
2971                        }) {
2972                            panes.push(pane.clone())
2973                        }
2974                    }
2975                    anyhow::Ok(panes)
2976                })??;
2977
2978                if let Some(view) = update_active_view.view {
2979                    for pane in panes_missing_view {
2980                        Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, cx)
2981                            .await?
2982                    }
2983                }
2984            }
2985            proto::update_followers::Variant::UpdateView(update_view) => {
2986                let variant = update_view
2987                    .variant
2988                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2989                let id = update_view
2990                    .id
2991                    .ok_or_else(|| anyhow!("missing update view id"))?;
2992                let mut tasks = Vec::new();
2993                this.update(cx, |this, cx| {
2994                    let project = this.project.clone();
2995                    for (_, state) in &mut this.follower_states {
2996                        if state.leader_id == leader_id {
2997                            let view_id = ViewId::from_proto(id.clone())?;
2998                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2999                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
3000                            }
3001                        }
3002                    }
3003                    anyhow::Ok(())
3004                })??;
3005                try_join_all(tasks).await.log_err();
3006            }
3007            proto::update_followers::Variant::CreateView(view) => {
3008                let panes = this.update(cx, |this, _| {
3009                    this.follower_states
3010                        .iter()
3011                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
3012                        .cloned()
3013                        .collect()
3014                })?;
3015                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
3016            }
3017        }
3018        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
3019        Ok(())
3020    }
3021
3022    async fn add_view_from_leader(
3023        this: WeakView<Self>,
3024        leader_id: PeerId,
3025        pane: View<Pane>,
3026        view: &proto::View,
3027        cx: &mut AsyncWindowContext,
3028    ) -> Result<()> {
3029        let this = this.upgrade().context("workspace dropped")?;
3030
3031        let item_builders = cx.update(|cx| {
3032            cx.default_global::<FollowableItemBuilders>()
3033                .values()
3034                .map(|b| b.0)
3035                .collect::<Vec<_>>()
3036        })?;
3037
3038        let Some(id) = view.id.clone() else {
3039            return Err(anyhow!("no id for view"));
3040        };
3041        let id = ViewId::from_proto(id)?;
3042
3043        let mut variant = view.variant.clone();
3044        if variant.is_none() {
3045            Err(anyhow!("missing view variant"))?;
3046        }
3047
3048        let task = item_builders.iter().find_map(|build_item| {
3049            cx.update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx))
3050                .log_err()
3051                .flatten()
3052        });
3053        let Some(task) = task else {
3054            return Err(anyhow!(
3055                "failed to construct view from leader (maybe from a different version of zed?)"
3056            ));
3057        };
3058
3059        let item = task.await?;
3060
3061        this.update(cx, |this, cx| {
3062            let state = this.follower_states.get_mut(&pane)?;
3063            item.set_leader_peer_id(Some(leader_id), cx);
3064            state.items_by_leader_view_id.insert(id, item);
3065
3066            Some(())
3067        })?;
3068
3069        Ok(())
3070    }
3071
3072    async fn add_views_from_leader(
3073        this: WeakView<Self>,
3074        leader_id: PeerId,
3075        panes: Vec<View<Pane>>,
3076        views: Vec<proto::View>,
3077        cx: &mut AsyncWindowContext,
3078    ) -> Result<()> {
3079        let this = this.upgrade().context("workspace dropped")?;
3080
3081        let item_builders = cx.update(|cx| {
3082            cx.default_global::<FollowableItemBuilders>()
3083                .values()
3084                .map(|b| b.0)
3085                .collect::<Vec<_>>()
3086        })?;
3087
3088        let mut item_tasks_by_pane = HashMap::default();
3089        for pane in panes {
3090            let mut item_tasks = Vec::new();
3091            let mut leader_view_ids = Vec::new();
3092            for view in &views {
3093                let Some(id) = &view.id else {
3094                    continue;
3095                };
3096                let id = ViewId::from_proto(id.clone())?;
3097                let mut variant = view.variant.clone();
3098                if variant.is_none() {
3099                    Err(anyhow!("missing view variant"))?;
3100                }
3101                for build_item in &item_builders {
3102                    let task = cx.update(|cx| {
3103                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
3104                    })?;
3105                    if let Some(task) = task {
3106                        item_tasks.push(task);
3107                        leader_view_ids.push(id);
3108                        break;
3109                    } else if variant.is_none() {
3110                        Err(anyhow!(
3111                            "failed to construct view from leader (maybe from a different version of zed?)"
3112                        ))?;
3113                    }
3114                }
3115            }
3116
3117            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3118        }
3119
3120        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3121            let items = futures::future::try_join_all(item_tasks).await?;
3122            this.update(cx, |this, cx| {
3123                let state = this.follower_states.get_mut(&pane)?;
3124                for (id, item) in leader_view_ids.into_iter().zip(items) {
3125                    item.set_leader_peer_id(Some(leader_id), cx);
3126                    state.items_by_leader_view_id.insert(id, item);
3127                }
3128
3129                Some(())
3130            })?;
3131        }
3132        Ok(())
3133    }
3134
3135    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3136        let mut is_project_item = true;
3137        let mut update = proto::UpdateActiveView::default();
3138        if cx.is_window_active() {
3139            if let Some(item) = self.active_item(cx) {
3140                if item.focus_handle(cx).contains_focused(cx) {
3141                    let leader_id = self
3142                        .pane_for(&*item)
3143                        .and_then(|pane| self.leader_for_pane(&pane));
3144
3145                    if let Some(item) = item.to_followable_item_handle(cx) {
3146                        let id = item
3147                            .remote_id(&self.app_state.client, cx)
3148                            .map(|id| id.to_proto());
3149
3150                        if let Some(id) = id.clone() {
3151                            if let Some(variant) = item.to_state_proto(cx) {
3152                                let view = Some(proto::View {
3153                                    id: Some(id.clone()),
3154                                    leader_id,
3155                                    variant: Some(variant),
3156                                });
3157
3158                                is_project_item = item.is_project_item(cx);
3159                                update = proto::UpdateActiveView {
3160                                    view,
3161                                    // TODO: once v0.124.0 is retired we can stop sending these
3162                                    id: Some(id),
3163                                    leader_id,
3164                                };
3165                            }
3166                        };
3167                    }
3168                }
3169            }
3170        }
3171
3172        if &update.id != &self.last_active_view_id {
3173            self.last_active_view_id = update.id.clone();
3174            self.update_followers(
3175                is_project_item,
3176                proto::update_followers::Variant::UpdateActiveView(update),
3177                cx,
3178            );
3179        }
3180    }
3181
3182    fn update_followers(
3183        &self,
3184        project_only: bool,
3185        update: proto::update_followers::Variant,
3186        cx: &mut WindowContext,
3187    ) -> Option<()> {
3188        // If this update only applies to for followers in the current project,
3189        // then skip it unless this project is shared. If it applies to all
3190        // followers, regardless of project, then set `project_id` to none,
3191        // indicating that it goes to all followers.
3192        let project_id = if project_only {
3193            Some(self.project.read(cx).remote_id()?)
3194        } else {
3195            None
3196        };
3197        self.app_state().workspace_store.update(cx, |store, cx| {
3198            store.update_followers(project_id, update, cx)
3199        })
3200    }
3201
3202    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3203        self.follower_states.get(pane).map(|state| state.leader_id)
3204    }
3205
3206    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3207        cx.notify();
3208
3209        let call = self.active_call()?;
3210        let room = call.read(cx).room()?.read(cx);
3211        let participant = room.remote_participant_for_peer_id(leader_id)?;
3212        let mut items_to_activate = Vec::new();
3213
3214        let leader_in_this_app;
3215        let leader_in_this_project;
3216        match participant.location {
3217            call::ParticipantLocation::SharedProject { project_id } => {
3218                leader_in_this_app = true;
3219                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3220            }
3221            call::ParticipantLocation::UnsharedProject => {
3222                leader_in_this_app = true;
3223                leader_in_this_project = false;
3224            }
3225            call::ParticipantLocation::External => {
3226                leader_in_this_app = false;
3227                leader_in_this_project = false;
3228            }
3229        };
3230
3231        for (pane, state) in &self.follower_states {
3232            if state.leader_id != leader_id {
3233                continue;
3234            }
3235            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3236                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3237                    if leader_in_this_project || !item.is_project_item(cx) {
3238                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3239                    }
3240                }
3241                continue;
3242            }
3243
3244            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3245                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3246            }
3247        }
3248
3249        for (pane, item) in items_to_activate {
3250            let pane_was_focused = pane.read(cx).has_focus(cx);
3251            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3252                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3253            } else {
3254                pane.update(cx, |pane, cx| {
3255                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3256                });
3257            }
3258
3259            if pane_was_focused {
3260                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3261            }
3262        }
3263
3264        None
3265    }
3266
3267    fn shared_screen_for_peer(
3268        &self,
3269        peer_id: PeerId,
3270        pane: &View<Pane>,
3271        cx: &mut WindowContext,
3272    ) -> Option<View<SharedScreen>> {
3273        let call = self.active_call()?;
3274        let room = call.read(cx).room()?.read(cx);
3275        let participant = room.remote_participant_for_peer_id(peer_id)?;
3276        let track = participant.video_tracks.values().next()?.clone();
3277        let user = participant.user.clone();
3278
3279        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3280            if item.read(cx).peer_id == peer_id {
3281                return Some(item);
3282            }
3283        }
3284
3285        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3286    }
3287
3288    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3289        if cx.is_window_active() {
3290            self.update_active_view_for_followers(cx);
3291            cx.background_executor()
3292                .spawn(persistence::DB.update_timestamp(self.database_id()))
3293                .detach();
3294        } else {
3295            for pane in &self.panes {
3296                pane.update(cx, |pane, cx| {
3297                    if let Some(item) = pane.active_item() {
3298                        item.workspace_deactivated(cx);
3299                    }
3300                    if matches!(
3301                        WorkspaceSettings::get_global(cx).autosave,
3302                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3303                    ) {
3304                        for item in pane.items() {
3305                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3306                                .detach_and_log_err(cx);
3307                        }
3308                    }
3309                });
3310            }
3311        }
3312    }
3313
3314    fn active_call(&self) -> Option<&Model<ActiveCall>> {
3315        self.active_call.as_ref().map(|(call, _)| call)
3316    }
3317
3318    fn on_active_call_event(
3319        &mut self,
3320        _: Model<ActiveCall>,
3321        event: &call::room::Event,
3322        cx: &mut ViewContext<Self>,
3323    ) {
3324        match event {
3325            call::room::Event::ParticipantLocationChanged { participant_id }
3326            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3327                self.leader_updated(*participant_id, cx);
3328            }
3329            _ => {}
3330        }
3331    }
3332
3333    pub fn database_id(&self) -> WorkspaceId {
3334        self.database_id
3335    }
3336
3337    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3338        let project = self.project().read(cx);
3339
3340        if project.is_local() {
3341            Some(
3342                project
3343                    .visible_worktrees(cx)
3344                    .map(|worktree| worktree.read(cx).abs_path())
3345                    .collect::<Vec<_>>()
3346                    .into(),
3347            )
3348        } else {
3349            None
3350        }
3351    }
3352
3353    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3354        match member {
3355            Member::Axis(PaneAxis { members, .. }) => {
3356                for child in members.iter() {
3357                    self.remove_panes(child.clone(), cx)
3358                }
3359            }
3360            Member::Pane(pane) => {
3361                self.force_remove_pane(&pane, cx);
3362            }
3363        }
3364    }
3365
3366    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3367        self.panes.retain(|p| p != pane);
3368        self.panes
3369            .last()
3370            .unwrap()
3371            .update(cx, |pane, cx| pane.focus(cx));
3372        if self.last_active_center_pane == Some(pane.downgrade()) {
3373            self.last_active_center_pane = None;
3374        }
3375        cx.notify();
3376    }
3377
3378    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3379        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3380            cx.background_executor()
3381                .timer(Duration::from_millis(100))
3382                .await;
3383            this.update(&mut cx, |this, cx| this.serialize_workspace(cx).detach())
3384                .log_err();
3385        }));
3386    }
3387
3388    fn serialize_workspace(&self, cx: &mut WindowContext) -> Task<()> {
3389        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3390            let (items, active) = {
3391                let pane = pane_handle.read(cx);
3392                let active_item_id = pane.active_item().map(|item| item.item_id());
3393                (
3394                    pane.items()
3395                        .filter_map(|item_handle| {
3396                            Some(SerializedItem {
3397                                kind: Arc::from(item_handle.serialized_item_kind()?),
3398                                item_id: item_handle.item_id().as_u64(),
3399                                active: Some(item_handle.item_id()) == active_item_id,
3400                            })
3401                        })
3402                        .collect::<Vec<_>>(),
3403                    pane.has_focus(cx),
3404                )
3405            };
3406
3407            SerializedPane::new(items, active)
3408        }
3409
3410        fn build_serialized_pane_group(
3411            pane_group: &Member,
3412            cx: &WindowContext,
3413        ) -> SerializedPaneGroup {
3414            match pane_group {
3415                Member::Axis(PaneAxis {
3416                    axis,
3417                    members,
3418                    flexes,
3419                    bounding_boxes: _,
3420                }) => SerializedPaneGroup::Group {
3421                    axis: SerializedAxis(*axis),
3422                    children: members
3423                        .iter()
3424                        .map(|member| build_serialized_pane_group(member, cx))
3425                        .collect::<Vec<_>>(),
3426                    flexes: Some(flexes.lock().clone()),
3427                },
3428                Member::Pane(pane_handle) => {
3429                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3430                }
3431            }
3432        }
3433
3434        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3435            let left_dock = this.left_dock.read(cx);
3436            let left_visible = left_dock.is_open();
3437            let left_active_panel = left_dock
3438                .visible_panel()
3439                .map(|panel| panel.persistent_name().to_string());
3440            let left_dock_zoom = left_dock
3441                .visible_panel()
3442                .map(|panel| panel.is_zoomed(cx))
3443                .unwrap_or(false);
3444
3445            let right_dock = this.right_dock.read(cx);
3446            let right_visible = right_dock.is_open();
3447            let right_active_panel = right_dock
3448                .visible_panel()
3449                .map(|panel| panel.persistent_name().to_string());
3450            let right_dock_zoom = right_dock
3451                .visible_panel()
3452                .map(|panel| panel.is_zoomed(cx))
3453                .unwrap_or(false);
3454
3455            let bottom_dock = this.bottom_dock.read(cx);
3456            let bottom_visible = bottom_dock.is_open();
3457            let bottom_active_panel = bottom_dock
3458                .visible_panel()
3459                .map(|panel| panel.persistent_name().to_string());
3460            let bottom_dock_zoom = bottom_dock
3461                .visible_panel()
3462                .map(|panel| panel.is_zoomed(cx))
3463                .unwrap_or(false);
3464
3465            DockStructure {
3466                left: DockData {
3467                    visible: left_visible,
3468                    active_panel: left_active_panel,
3469                    zoom: left_dock_zoom,
3470                },
3471                right: DockData {
3472                    visible: right_visible,
3473                    active_panel: right_active_panel,
3474                    zoom: right_dock_zoom,
3475                },
3476                bottom: DockData {
3477                    visible: bottom_visible,
3478                    active_panel: bottom_active_panel,
3479                    zoom: bottom_dock_zoom,
3480                },
3481            }
3482        }
3483
3484        if let Some(location) = self.location(cx) {
3485            // Load bearing special case:
3486            //  - with_local_workspace() relies on this to not have other stuff open
3487            //    when you open your log
3488            if !location.paths().is_empty() {
3489                let center_group = build_serialized_pane_group(&self.center.root, cx);
3490                let docks = build_serialized_docks(self, cx);
3491                let serialized_workspace = SerializedWorkspace {
3492                    id: self.database_id,
3493                    location,
3494                    center_group,
3495                    bounds: Default::default(),
3496                    display: Default::default(),
3497                    docks,
3498                    fullscreen: cx.is_fullscreen(),
3499                };
3500                return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
3501            }
3502        }
3503        Task::ready(())
3504    }
3505
3506    fn refresh_recent_documents(&self, cx: &mut AppContext) -> Task<Result<()>> {
3507        if !self.project.read(cx).is_local() {
3508            return Task::ready(Ok(()));
3509        }
3510        cx.spawn(|cx| async move {
3511            let recents = WORKSPACE_DB
3512                .recent_workspaces_on_disk()
3513                .await
3514                .unwrap_or_default();
3515            let mut unique_paths = HashMap::default();
3516            for (id, workspace) in &recents {
3517                for path in workspace.paths().iter() {
3518                    unique_paths.insert(path.clone(), id);
3519                }
3520            }
3521            let current_paths = unique_paths
3522                .into_iter()
3523                .sorted_by_key(|(_, id)| *id)
3524                .map(|(path, _)| path)
3525                .collect::<Vec<_>>();
3526            cx.update(|cx| {
3527                cx.clear_recent_documents();
3528                cx.add_recent_documents(&current_paths);
3529            })
3530        })
3531    }
3532
3533    pub(crate) fn load_workspace(
3534        serialized_workspace: SerializedWorkspace,
3535        paths_to_open: Vec<Option<ProjectPath>>,
3536        cx: &mut ViewContext<Workspace>,
3537    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3538        cx.spawn(|workspace, mut cx| async move {
3539            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3540
3541            let mut center_group = None;
3542            let mut center_items = None;
3543
3544            // Traverse the splits tree and add to things
3545            if let Some((group, active_pane, items)) = serialized_workspace
3546                .center_group
3547                .deserialize(
3548                    &project,
3549                    serialized_workspace.id,
3550                    workspace.clone(),
3551                    &mut cx,
3552                )
3553                .await
3554            {
3555                center_items = Some(items);
3556                center_group = Some((group, active_pane))
3557            }
3558
3559            let mut items_by_project_path = cx.update(|cx| {
3560                center_items
3561                    .unwrap_or_default()
3562                    .into_iter()
3563                    .filter_map(|item| {
3564                        let item = item?;
3565                        let project_path = item.project_path(cx)?;
3566                        Some((project_path, item))
3567                    })
3568                    .collect::<HashMap<_, _>>()
3569            })?;
3570
3571            let opened_items = paths_to_open
3572                .into_iter()
3573                .map(|path_to_open| {
3574                    path_to_open
3575                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3576                })
3577                .collect::<Vec<_>>();
3578
3579            // Remove old panes from workspace panes list
3580            workspace.update(&mut cx, |workspace, cx| {
3581                if let Some((center_group, active_pane)) = center_group {
3582                    workspace.remove_panes(workspace.center.root.clone(), cx);
3583
3584                    // Swap workspace center group
3585                    workspace.center = PaneGroup::with_root(center_group);
3586                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3587                    if let Some(active_pane) = active_pane {
3588                        workspace.active_pane = active_pane;
3589                        cx.focus_self();
3590                    } else {
3591                        workspace.active_pane = workspace.center.first_pane().clone();
3592                    }
3593                }
3594
3595                let docks = serialized_workspace.docks;
3596
3597                let right = docks.right.clone();
3598                workspace
3599                    .right_dock
3600                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3601                let left = docks.left.clone();
3602                workspace
3603                    .left_dock
3604                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3605                let bottom = docks.bottom.clone();
3606                workspace
3607                    .bottom_dock
3608                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3609
3610                cx.notify();
3611            })?;
3612
3613            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3614            workspace.update(&mut cx, |workspace, cx| {
3615                workspace.serialize_workspace(cx).detach()
3616            })?;
3617
3618            Ok(opened_items)
3619        })
3620    }
3621
3622    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3623        self.add_workspace_actions_listeners(div, cx)
3624            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3625            .on_action(cx.listener(Self::close_all_items_and_panes))
3626            .on_action(cx.listener(Self::save_all))
3627            .on_action(cx.listener(Self::send_keystrokes))
3628            .on_action(cx.listener(Self::add_folder_to_project))
3629            .on_action(cx.listener(Self::follow_next_collaborator))
3630            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3631                let pane = workspace.active_pane().clone();
3632                workspace.unfollow(&pane, cx);
3633            }))
3634            .on_action(cx.listener(|workspace, action: &Save, cx| {
3635                workspace
3636                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3637                    .detach_and_log_err(cx);
3638            }))
3639            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
3640                workspace
3641                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
3642                    .detach_and_log_err(cx);
3643            }))
3644            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3645                workspace
3646                    .save_active_item(SaveIntent::SaveAs, cx)
3647                    .detach_and_log_err(cx);
3648            }))
3649            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3650                workspace.activate_previous_pane(cx)
3651            }))
3652            .on_action(
3653                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3654            )
3655            .on_action(
3656                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3657                    workspace.activate_pane_in_direction(action.0, cx)
3658                }),
3659            )
3660            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3661                workspace.swap_pane_in_direction(action.0, cx)
3662            }))
3663            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3664                this.toggle_dock(DockPosition::Left, cx);
3665            }))
3666            .on_action(
3667                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3668                    workspace.toggle_dock(DockPosition::Right, cx);
3669                }),
3670            )
3671            .on_action(
3672                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3673                    workspace.toggle_dock(DockPosition::Bottom, cx);
3674                }),
3675            )
3676            .on_action(
3677                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3678                    workspace.close_all_docks(cx);
3679                }),
3680            )
3681            .on_action(cx.listener(Workspace::open))
3682            .on_action(cx.listener(Workspace::close_window))
3683            .on_action(cx.listener(Workspace::activate_pane_at_index))
3684            .on_action(
3685                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3686                    workspace.reopen_closed_item(cx).detach();
3687                }),
3688            )
3689    }
3690
3691    #[cfg(any(test, feature = "test-support"))]
3692    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3693        use node_runtime::FakeNodeRuntime;
3694
3695        let client = project.read(cx).client();
3696        let user_store = project.read(cx).user_store();
3697
3698        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3699        cx.activate_window();
3700        let app_state = Arc::new(AppState {
3701            languages: project.read(cx).languages().clone(),
3702            workspace_store,
3703            client,
3704            user_store,
3705            fs: project.read(cx).fs().clone(),
3706            build_window_options: |_, _| Default::default(),
3707            node_runtime: FakeNodeRuntime::new(),
3708        });
3709        let workspace = Self::new(Default::default(), project, app_state, cx);
3710        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3711        workspace
3712    }
3713
3714    pub fn register_action<A: Action>(
3715        &mut self,
3716        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3717    ) -> &mut Self {
3718        let callback = Arc::new(callback);
3719
3720        self.workspace_actions.push(Box::new(move |div, cx| {
3721            let callback = callback.clone();
3722            div.on_action(
3723                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3724            )
3725        }));
3726        self
3727    }
3728
3729    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3730        let mut div = div
3731            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3732            .on_action(cx.listener(Self::close_all_items_and_panes))
3733            .on_action(cx.listener(Self::add_folder_to_project))
3734            .on_action(cx.listener(Self::save_all))
3735            .on_action(cx.listener(Self::open));
3736        for action in self.workspace_actions.iter() {
3737            div = (action)(div, cx)
3738        }
3739        div
3740    }
3741
3742    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3743        self.modal_layer.read(cx).has_active_modal()
3744    }
3745
3746    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3747        self.modal_layer.read(cx).active_modal()
3748    }
3749
3750    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3751    where
3752        B: FnOnce(&mut ViewContext<V>) -> V,
3753    {
3754        self.modal_layer
3755            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3756    }
3757}
3758
3759fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<Bounds<GlobalPixels>> {
3760    let display_origin = cx
3761        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3762        .ok()??;
3763    ZED_WINDOW_POSITION
3764        .zip(*ZED_WINDOW_SIZE)
3765        .map(|(position, size)| Bounds {
3766            origin: display_origin + position,
3767            size,
3768        })
3769}
3770
3771fn open_items(
3772    serialized_workspace: Option<SerializedWorkspace>,
3773    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3774    app_state: Arc<AppState>,
3775    cx: &mut ViewContext<Workspace>,
3776) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3777    let restored_items = serialized_workspace.map(|serialized_workspace| {
3778        Workspace::load_workspace(
3779            serialized_workspace,
3780            project_paths_to_open
3781                .iter()
3782                .map(|(_, project_path)| project_path)
3783                .cloned()
3784                .collect(),
3785            cx,
3786        )
3787    });
3788
3789    cx.spawn(|workspace, mut cx| async move {
3790        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3791
3792        if let Some(restored_items) = restored_items {
3793            let restored_items = restored_items.await?;
3794
3795            let restored_project_paths = restored_items
3796                .iter()
3797                .filter_map(|item| {
3798                    cx.update(|cx| item.as_ref()?.project_path(cx))
3799                        .ok()
3800                        .flatten()
3801                })
3802                .collect::<HashSet<_>>();
3803
3804            for restored_item in restored_items {
3805                opened_items.push(restored_item.map(Ok));
3806            }
3807
3808            project_paths_to_open
3809                .iter_mut()
3810                .for_each(|(_, project_path)| {
3811                    if let Some(project_path_to_open) = project_path {
3812                        if restored_project_paths.contains(project_path_to_open) {
3813                            *project_path = None;
3814                        }
3815                    }
3816                });
3817        } else {
3818            for _ in 0..project_paths_to_open.len() {
3819                opened_items.push(None);
3820            }
3821        }
3822        assert!(opened_items.len() == project_paths_to_open.len());
3823
3824        let tasks =
3825            project_paths_to_open
3826                .into_iter()
3827                .enumerate()
3828                .map(|(ix, (abs_path, project_path))| {
3829                    let workspace = workspace.clone();
3830                    cx.spawn(|mut cx| {
3831                        let fs = app_state.fs.clone();
3832                        async move {
3833                            let file_project_path = project_path?;
3834                            if fs.is_dir(&abs_path).await {
3835                                None
3836                            } else {
3837                                Some((
3838                                    ix,
3839                                    workspace
3840                                        .update(&mut cx, |workspace, cx| {
3841                                            workspace.open_path(file_project_path, None, true, cx)
3842                                        })
3843                                        .log_err()?
3844                                        .await,
3845                                ))
3846                            }
3847                        }
3848                    })
3849                });
3850
3851        let tasks = tasks.collect::<Vec<_>>();
3852
3853        let tasks = futures::future::join_all(tasks);
3854        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
3855            opened_items[ix] = Some(path_open_result);
3856        }
3857
3858        Ok(opened_items)
3859    })
3860}
3861
3862enum ActivateInDirectionTarget {
3863    Pane(View<Pane>),
3864    Dock(View<Dock>),
3865}
3866
3867fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3868    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3869
3870    workspace
3871        .update(cx, |workspace, cx| {
3872            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3873                workspace.show_notification_once(0, cx, |cx| {
3874                    cx.new_view(|_| {
3875                        MessageNotification::new("Failed to load the database file.")
3876                            .with_click_message("Click to let us know about this error")
3877                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3878                    })
3879                });
3880            }
3881        })
3882        .log_err();
3883}
3884
3885impl FocusableView for Workspace {
3886    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3887        self.active_pane.focus_handle(cx)
3888    }
3889}
3890
3891#[derive(Clone, Render)]
3892struct DraggedDock(DockPosition);
3893
3894impl Render for Workspace {
3895    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3896        let mut context = KeyContext::default();
3897        context.add("Workspace");
3898
3899        let (ui_font, ui_font_size) = {
3900            let theme_settings = ThemeSettings::get_global(cx);
3901            (
3902                theme_settings.ui_font.family.clone(),
3903                theme_settings.ui_font_size,
3904            )
3905        };
3906
3907        let theme = cx.theme().clone();
3908        let colors = theme.colors();
3909        cx.set_rem_size(ui_font_size);
3910
3911        self.actions(div(), cx)
3912            .key_context(context)
3913            .relative()
3914            .size_full()
3915            .flex()
3916            .flex_col()
3917            .font(ui_font)
3918            .gap_0()
3919            .justify_start()
3920            .items_start()
3921            .text_color(colors.text)
3922            .bg(colors.background)
3923            .children(self.titlebar_item.clone())
3924            .child(
3925                div()
3926                    .id("workspace")
3927                    .relative()
3928                    .flex_1()
3929                    .w_full()
3930                    .flex()
3931                    .flex_col()
3932                    .overflow_hidden()
3933                    .border_t()
3934                    .border_b()
3935                    .border_color(colors.border)
3936                    .child({
3937                        let this = cx.view().clone();
3938                        canvas(
3939                            move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
3940                            |_, _, _| {},
3941                        )
3942                        .absolute()
3943                        .size_full()
3944                    })
3945                    .on_drag_move(
3946                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3947                            match e.drag(cx).0 {
3948                                DockPosition::Left => {
3949                                    let size = workspace.bounds.left() + e.event.position.x;
3950                                    workspace.left_dock.update(cx, |left_dock, cx| {
3951                                        left_dock.resize_active_panel(Some(size), cx);
3952                                    });
3953                                }
3954                                DockPosition::Right => {
3955                                    let size = workspace.bounds.right() - e.event.position.x;
3956                                    workspace.right_dock.update(cx, |right_dock, cx| {
3957                                        right_dock.resize_active_panel(Some(size), cx);
3958                                    });
3959                                }
3960                                DockPosition::Bottom => {
3961                                    let size = workspace.bounds.bottom() - e.event.position.y;
3962                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3963                                        bottom_dock.resize_active_panel(Some(size), cx);
3964                                    });
3965                                }
3966                            }
3967                        }),
3968                    )
3969                    .child(
3970                        div()
3971                            .flex()
3972                            .flex_row()
3973                            .h_full()
3974                            // Left Dock
3975                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3976                                || {
3977                                    div()
3978                                        .flex()
3979                                        .flex_none()
3980                                        .overflow_hidden()
3981                                        .child(self.left_dock.clone())
3982                                },
3983                            ))
3984                            // Panes
3985                            .child(
3986                                div()
3987                                    .flex()
3988                                    .flex_col()
3989                                    .flex_1()
3990                                    .overflow_hidden()
3991                                    .child(self.center.render(
3992                                        &self.project,
3993                                        &self.follower_states,
3994                                        self.active_call(),
3995                                        &self.active_pane,
3996                                        self.zoomed.as_ref(),
3997                                        &self.app_state,
3998                                        cx,
3999                                    ))
4000                                    .children(
4001                                        self.zoomed_position
4002                                            .ne(&Some(DockPosition::Bottom))
4003                                            .then(|| self.bottom_dock.clone()),
4004                                    ),
4005                            )
4006                            // Right Dock
4007                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
4008                                || {
4009                                    div()
4010                                        .flex()
4011                                        .flex_none()
4012                                        .overflow_hidden()
4013                                        .child(self.right_dock.clone())
4014                                },
4015                            )),
4016                    )
4017                    .children(self.zoomed.as_ref().and_then(|view| {
4018                        let zoomed_view = view.upgrade()?;
4019                        let div = div()
4020                            .occlude()
4021                            .absolute()
4022                            .overflow_hidden()
4023                            .border_color(colors.border)
4024                            .bg(colors.background)
4025                            .child(zoomed_view)
4026                            .inset_0()
4027                            .shadow_lg();
4028
4029                        Some(match self.zoomed_position {
4030                            Some(DockPosition::Left) => div.right_2().border_r(),
4031                            Some(DockPosition::Right) => div.left_2().border_l(),
4032                            Some(DockPosition::Bottom) => div.top_2().border_t(),
4033                            None => div.top_2().bottom_2().left_2().right_2().border(),
4034                        })
4035                    }))
4036                    .child(self.modal_layer.clone())
4037                    .children(self.render_notifications(cx)),
4038            )
4039            .child(self.status_bar.clone())
4040            .children(if self.project.read(cx).is_disconnected() {
4041                Some(DisconnectedOverlay)
4042            } else {
4043                None
4044            })
4045    }
4046}
4047
4048impl WorkspaceStore {
4049    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4050        Self {
4051            workspaces: Default::default(),
4052            _subscriptions: vec![
4053                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4054                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4055            ],
4056            client,
4057        }
4058    }
4059
4060    pub fn update_followers(
4061        &self,
4062        project_id: Option<u64>,
4063        update: proto::update_followers::Variant,
4064        cx: &AppContext,
4065    ) -> Option<()> {
4066        let active_call = ActiveCall::try_global(cx)?;
4067        let room_id = active_call.read(cx).room()?.read(cx).id();
4068        self.client
4069            .send(proto::UpdateFollowers {
4070                room_id,
4071                project_id,
4072                variant: Some(update),
4073            })
4074            .log_err()
4075    }
4076
4077    pub async fn handle_follow(
4078        this: Model<Self>,
4079        envelope: TypedEnvelope<proto::Follow>,
4080        _: Arc<Client>,
4081        mut cx: AsyncAppContext,
4082    ) -> Result<proto::FollowResponse> {
4083        this.update(&mut cx, |this, cx| {
4084            let follower = Follower {
4085                project_id: envelope.payload.project_id,
4086                peer_id: envelope.original_sender_id()?,
4087            };
4088
4089            let mut response = proto::FollowResponse::default();
4090            this.workspaces.retain(|workspace| {
4091                workspace
4092                    .update(cx, |workspace, cx| {
4093                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4094                        if response.views.is_empty() {
4095                            response.views = handler_response.views;
4096                        } else {
4097                            response.views.extend_from_slice(&handler_response.views);
4098                        }
4099
4100                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
4101                            if response.active_view_id.is_none()
4102                                || workspace.project.read(cx).remote_id() == follower.project_id
4103                            {
4104                                response.active_view_id = Some(active_view_id);
4105                            }
4106                        }
4107
4108                        if let Some(active_view) = handler_response.active_view.clone() {
4109                            if response.active_view_id.is_none()
4110                                || workspace.project.read(cx).remote_id() == follower.project_id
4111                            {
4112                                response.active_view = Some(active_view)
4113                            }
4114                        }
4115                    })
4116                    .is_ok()
4117            });
4118
4119            Ok(response)
4120        })?
4121    }
4122
4123    async fn handle_update_followers(
4124        this: Model<Self>,
4125        envelope: TypedEnvelope<proto::UpdateFollowers>,
4126        _: Arc<Client>,
4127        mut cx: AsyncAppContext,
4128    ) -> Result<()> {
4129        let leader_id = envelope.original_sender_id()?;
4130        let update = envelope.payload;
4131
4132        this.update(&mut cx, |this, cx| {
4133            this.workspaces.retain(|workspace| {
4134                workspace
4135                    .update(cx, |workspace, cx| {
4136                        let project_id = workspace.project.read(cx).remote_id();
4137                        if update.project_id != project_id && update.project_id.is_some() {
4138                            return;
4139                        }
4140                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4141                    })
4142                    .is_ok()
4143            });
4144            Ok(())
4145        })?
4146    }
4147}
4148
4149impl ViewId {
4150    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4151        Ok(Self {
4152            creator: message
4153                .creator
4154                .ok_or_else(|| anyhow!("creator is missing"))?,
4155            id: message.id,
4156        })
4157    }
4158
4159    pub(crate) fn to_proto(&self) -> proto::ViewId {
4160        proto::ViewId {
4161            creator: Some(self.creator),
4162            id: self.id,
4163        }
4164    }
4165}
4166
4167pub trait WorkspaceHandle {
4168    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4169}
4170
4171impl WorkspaceHandle for View<Workspace> {
4172    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4173        self.read(cx)
4174            .worktrees(cx)
4175            .flat_map(|worktree| {
4176                let worktree_id = worktree.read(cx).id();
4177                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4178                    worktree_id,
4179                    path: f.path.clone(),
4180                })
4181            })
4182            .collect::<Vec<_>>()
4183    }
4184}
4185
4186impl std::fmt::Debug for OpenPaths {
4187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4188        f.debug_struct("OpenPaths")
4189            .field("paths", &self.paths)
4190            .finish()
4191    }
4192}
4193
4194pub fn activate_workspace_for_project(
4195    cx: &mut AppContext,
4196    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4197) -> Option<WindowHandle<Workspace>> {
4198    for window in cx.windows() {
4199        let Some(workspace) = window.downcast::<Workspace>() else {
4200            continue;
4201        };
4202
4203        let predicate = workspace
4204            .update(cx, |workspace, cx| {
4205                let project = workspace.project.read(cx);
4206                if predicate(project, cx) {
4207                    cx.activate_window();
4208                    true
4209                } else {
4210                    false
4211                }
4212            })
4213            .log_err()
4214            .unwrap_or(false);
4215
4216        if predicate {
4217            return Some(workspace);
4218        }
4219    }
4220
4221    None
4222}
4223
4224pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4225    DB.last_workspace().await.log_err().flatten()
4226}
4227
4228actions!(collab, [OpenChannelNotes]);
4229actions!(zed, [OpenLog]);
4230
4231async fn join_channel_internal(
4232    channel_id: ChannelId,
4233    app_state: &Arc<AppState>,
4234    requesting_window: Option<WindowHandle<Workspace>>,
4235    active_call: &Model<ActiveCall>,
4236    cx: &mut AsyncAppContext,
4237) -> Result<bool> {
4238    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4239        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4240            return (false, None);
4241        };
4242
4243        let already_in_channel = room.channel_id() == Some(channel_id);
4244        let should_prompt = room.is_sharing_project()
4245            && room.remote_participants().len() > 0
4246            && !already_in_channel;
4247        let open_room = if already_in_channel {
4248            active_call.room().cloned()
4249        } else {
4250            None
4251        };
4252        (should_prompt, open_room)
4253    })?;
4254
4255    if let Some(room) = open_room {
4256        let task = room.update(cx, |room, cx| {
4257            if let Some((project, host)) = room.most_active_project(cx) {
4258                return Some(join_in_room_project(project, host, app_state.clone(), cx));
4259            }
4260
4261            None
4262        })?;
4263        if let Some(task) = task {
4264            task.await?;
4265        }
4266        return anyhow::Ok(true);
4267    }
4268
4269    if should_prompt {
4270        if let Some(workspace) = requesting_window {
4271            let answer = workspace
4272                .update(cx, |_, cx| {
4273                    cx.prompt(
4274                        PromptLevel::Warning,
4275                        "Do you want to switch channels?",
4276                        Some("Leaving this call will unshare your current project."),
4277                        &["Yes, Join Channel", "Cancel"],
4278                    )
4279                })?
4280                .await;
4281
4282            if answer == Ok(1) {
4283                return Ok(false);
4284            }
4285        } else {
4286            return Ok(false); // unreachable!() hopefully
4287        }
4288    }
4289
4290    let client = cx.update(|cx| active_call.read(cx).client())?;
4291
4292    let mut client_status = client.status();
4293
4294    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4295    'outer: loop {
4296        let Some(status) = client_status.recv().await else {
4297            return Err(anyhow!("error connecting"));
4298        };
4299
4300        match status {
4301            Status::Connecting
4302            | Status::Authenticating
4303            | Status::Reconnecting
4304            | Status::Reauthenticating => continue,
4305            Status::Connected { .. } => break 'outer,
4306            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4307            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4308            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4309                return Err(ErrorCode::Disconnected.into());
4310            }
4311        }
4312    }
4313
4314    let room = active_call
4315        .update(cx, |active_call, cx| {
4316            active_call.join_channel(channel_id, cx)
4317        })?
4318        .await?;
4319
4320    let Some(room) = room else {
4321        return anyhow::Ok(true);
4322    };
4323
4324    room.update(cx, |room, _| room.room_update_completed())?
4325        .await;
4326
4327    let task = room.update(cx, |room, cx| {
4328        if let Some((project, host)) = room.most_active_project(cx) {
4329            return Some(join_in_room_project(project, host, app_state.clone(), cx));
4330        }
4331
4332        // if you are the first to join a channel, share your project
4333        if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
4334            if let Some(workspace) = requesting_window {
4335                let project = workspace.update(cx, |workspace, cx| {
4336                    if !CallSettings::get_global(cx).share_on_join {
4337                        return None;
4338                    }
4339                    let project = workspace.project.read(cx);
4340                    if project.is_local()
4341                        && project.visible_worktrees(cx).any(|tree| {
4342                            tree.read(cx)
4343                                .root_entry()
4344                                .map_or(false, |entry| entry.is_dir())
4345                        })
4346                    {
4347                        Some(workspace.project.clone())
4348                    } else {
4349                        None
4350                    }
4351                });
4352                if let Ok(Some(project)) = project {
4353                    return Some(cx.spawn(|room, mut cx| async move {
4354                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4355                            .await?;
4356                        Ok(())
4357                    }));
4358                }
4359            }
4360        }
4361
4362        None
4363    })?;
4364    if let Some(task) = task {
4365        task.await?;
4366        return anyhow::Ok(true);
4367    }
4368    anyhow::Ok(false)
4369}
4370
4371pub fn join_channel(
4372    channel_id: ChannelId,
4373    app_state: Arc<AppState>,
4374    requesting_window: Option<WindowHandle<Workspace>>,
4375    cx: &mut AppContext,
4376) -> Task<Result<()>> {
4377    let active_call = ActiveCall::global(cx);
4378    cx.spawn(|mut cx| async move {
4379        let result = join_channel_internal(
4380            channel_id,
4381            &app_state,
4382            requesting_window,
4383            &active_call,
4384            &mut cx,
4385        )
4386            .await;
4387
4388        // join channel succeeded, and opened a window
4389        if matches!(result, Ok(true)) {
4390            return anyhow::Ok(());
4391        }
4392
4393        // find an existing workspace to focus and show call controls
4394        let mut active_window =
4395            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4396        if active_window.is_none() {
4397            // no open workspaces, make one to show the error in (blergh)
4398            let (window_handle, _) = cx
4399                .update(|cx| {
4400                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4401                })?
4402                .await?;
4403
4404            if result.is_ok() {
4405                cx.update(|cx| {
4406                    cx.dispatch_action(&OpenChannelNotes);
4407                }).log_err();
4408            }
4409
4410            active_window = Some(window_handle);
4411        }
4412
4413        if let Err(err) = result {
4414            log::error!("failed to join channel: {}", err);
4415            if let Some(active_window) = active_window {
4416                active_window
4417                    .update(&mut cx, |_, cx| {
4418                        let detail: SharedString = match err.error_code() {
4419                            ErrorCode::SignedOut => {
4420                                "Please sign in to continue.".into()
4421                            }
4422                            ErrorCode::UpgradeRequired => {
4423                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4424                            }
4425                            ErrorCode::NoSuchChannel => {
4426                                "No matching channel was found. Please check the link and try again.".into()
4427                            }
4428                            ErrorCode::Forbidden => {
4429                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4430                            }
4431                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4432                            _ => format!("{}\n\nPlease try again.", err).into(),
4433                        };
4434                        cx.prompt(
4435                            PromptLevel::Critical,
4436                            "Failed to join channel",
4437                            Some(&detail),
4438                            &["Ok"],
4439                        )
4440                    })?
4441                    .await
4442                    .ok();
4443            }
4444        }
4445
4446        // return ok, we showed the error to the user.
4447        return anyhow::Ok(());
4448    })
4449}
4450
4451pub async fn get_any_active_workspace(
4452    app_state: Arc<AppState>,
4453    mut cx: AsyncAppContext,
4454) -> anyhow::Result<WindowHandle<Workspace>> {
4455    // find an existing workspace to focus and show call controls
4456    let active_window = activate_any_workspace_window(&mut cx);
4457    if active_window.is_none() {
4458        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4459            .await?;
4460    }
4461    activate_any_workspace_window(&mut cx).context("could not open zed")
4462}
4463
4464fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4465    cx.update(|cx| {
4466        if let Some(workspace_window) = cx
4467            .active_window()
4468            .and_then(|window| window.downcast::<Workspace>())
4469        {
4470            return Some(workspace_window);
4471        }
4472
4473        for window in cx.windows() {
4474            if let Some(workspace_window) = window.downcast::<Workspace>() {
4475                workspace_window
4476                    .update(cx, |_, cx| cx.activate_window())
4477                    .ok();
4478                return Some(workspace_window);
4479            }
4480        }
4481        None
4482    })
4483    .ok()
4484    .flatten()
4485}
4486
4487fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
4488    cx.windows()
4489        .into_iter()
4490        .filter_map(|window| window.downcast::<Workspace>())
4491        .filter(|workspace| {
4492            workspace
4493                .read(cx)
4494                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
4495        })
4496        .collect()
4497}
4498
4499#[derive(Default)]
4500pub struct OpenOptions {
4501    pub open_new_workspace: Option<bool>,
4502    pub replace_window: Option<WindowHandle<Workspace>>,
4503}
4504
4505#[allow(clippy::type_complexity)]
4506pub fn open_paths(
4507    abs_paths: &[PathBuf],
4508    app_state: Arc<AppState>,
4509    open_options: OpenOptions,
4510    cx: &mut AppContext,
4511) -> Task<
4512    anyhow::Result<(
4513        WindowHandle<Workspace>,
4514        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4515    )>,
4516> {
4517    let abs_paths = abs_paths.to_vec();
4518    let mut existing = None;
4519    let mut best_match = None;
4520    let mut open_visible = OpenVisible::All;
4521
4522    if open_options.open_new_workspace != Some(true) {
4523        for window in local_workspace_windows(cx) {
4524            if let Ok(workspace) = window.read(cx) {
4525                let m = workspace
4526                    .project
4527                    .read(cx)
4528                    .visibility_for_paths(&abs_paths, cx);
4529                if m > best_match {
4530                    existing = Some(window);
4531                    best_match = m;
4532                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
4533                    existing = Some(window)
4534                }
4535            }
4536        }
4537    }
4538
4539    cx.spawn(move |mut cx| async move {
4540        if open_options.open_new_workspace.is_none() && existing.is_none() {
4541            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
4542            if futures::future::join_all(all_files)
4543                .await
4544                .into_iter()
4545                .filter_map(|result| result.ok().flatten())
4546                .all(|file| !file.is_dir)
4547            {
4548                cx.update(|cx| {
4549                    for window in local_workspace_windows(cx) {
4550                        if let Ok(workspace) = window.read(cx) {
4551                            let project = workspace.project().read(cx);
4552                            if project.is_remote() {
4553                                continue;
4554                            }
4555                            existing = Some(window);
4556                            open_visible = OpenVisible::None;
4557                            break;
4558                        }
4559                    }
4560                })?;
4561            }
4562        }
4563
4564        if let Some(existing) = existing {
4565            Ok((
4566                existing,
4567                existing
4568                    .update(&mut cx, |workspace, cx| {
4569                        cx.activate_window();
4570                        workspace.open_paths(abs_paths, open_visible, None, cx)
4571                    })?
4572                    .await,
4573            ))
4574        } else {
4575            cx.update(move |cx| {
4576                Workspace::new_local(
4577                    abs_paths,
4578                    app_state.clone(),
4579                    open_options.replace_window,
4580                    cx,
4581                )
4582            })?
4583            .await
4584        }
4585    })
4586}
4587
4588pub fn open_new(
4589    app_state: Arc<AppState>,
4590    cx: &mut AppContext,
4591    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4592) -> Task<()> {
4593    let task = Workspace::new_local(Vec::new(), app_state, None, cx);
4594    cx.spawn(|mut cx| async move {
4595        if let Some((workspace, opened_paths)) = task.await.log_err() {
4596            workspace
4597                .update(&mut cx, |workspace, cx| {
4598                    if opened_paths.is_empty() {
4599                        init(workspace, cx)
4600                    }
4601                })
4602                .log_err();
4603        }
4604    })
4605}
4606
4607pub fn create_and_open_local_file(
4608    path: &'static Path,
4609    cx: &mut ViewContext<Workspace>,
4610    default_content: impl 'static + Send + FnOnce() -> Rope,
4611) -> Task<Result<Box<dyn ItemHandle>>> {
4612    cx.spawn(|workspace, mut cx| async move {
4613        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4614        if !fs.is_file(path).await {
4615            fs.create_file(path, Default::default()).await?;
4616            fs.save(path, &default_content(), Default::default())
4617                .await?;
4618        }
4619
4620        let mut items = workspace
4621            .update(&mut cx, |workspace, cx| {
4622                workspace.with_local_workspace(cx, |workspace, cx| {
4623                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4624                })
4625            })?
4626            .await?
4627            .await;
4628
4629        let item = items.pop().flatten();
4630        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4631    })
4632}
4633
4634pub fn join_hosted_project(
4635    hosted_project_id: ProjectId,
4636    app_state: Arc<AppState>,
4637    cx: &mut AppContext,
4638) -> Task<Result<()>> {
4639    cx.spawn(|mut cx| async move {
4640        let existing_window = cx.update(|cx| {
4641            cx.windows().into_iter().find_map(|window| {
4642                let workspace = window.downcast::<Workspace>()?;
4643                workspace
4644                    .read(cx)
4645                    .is_ok_and(|workspace| {
4646                        workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
4647                    })
4648                    .then(|| workspace)
4649            })
4650        })?;
4651
4652        let workspace = if let Some(existing_window) = existing_window {
4653            existing_window
4654        } else {
4655            let project = Project::hosted(
4656                hosted_project_id,
4657                app_state.user_store.clone(),
4658                app_state.client.clone(),
4659                app_state.languages.clone(),
4660                app_state.fs.clone(),
4661                cx.clone(),
4662            )
4663            .await?;
4664
4665            let window_bounds_override = window_bounds_env_override(&cx);
4666            cx.update(|cx| {
4667                let mut options = (app_state.build_window_options)(None, cx);
4668                options.bounds = window_bounds_override;
4669                cx.open_window(options, |cx| {
4670                    cx.new_view(|cx| {
4671                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4672                    })
4673                })
4674            })?
4675        };
4676
4677        workspace.update(&mut cx, |_, cx| {
4678            cx.activate(true);
4679            cx.activate_window();
4680        })?;
4681
4682        Ok(())
4683    })
4684}
4685
4686pub fn join_in_room_project(
4687    project_id: u64,
4688    follow_user_id: u64,
4689    app_state: Arc<AppState>,
4690    cx: &mut AppContext,
4691) -> Task<Result<()>> {
4692    let windows = cx.windows();
4693    cx.spawn(|mut cx| async move {
4694        let existing_workspace = windows.into_iter().find_map(|window| {
4695            window.downcast::<Workspace>().and_then(|window| {
4696                window
4697                    .update(&mut cx, |workspace, cx| {
4698                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4699                            Some(window)
4700                        } else {
4701                            None
4702                        }
4703                    })
4704                    .unwrap_or(None)
4705            })
4706        });
4707
4708        let workspace = if let Some(existing_workspace) = existing_workspace {
4709            existing_workspace
4710        } else {
4711            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4712            let room = active_call
4713                .read_with(&cx, |call, _| call.room().cloned())?
4714                .ok_or_else(|| anyhow!("not in a call"))?;
4715            let project = room
4716                .update(&mut cx, |room, cx| {
4717                    room.join_project(
4718                        project_id,
4719                        app_state.languages.clone(),
4720                        app_state.fs.clone(),
4721                        cx,
4722                    )
4723                })?
4724                .await?;
4725
4726            let window_bounds_override = window_bounds_env_override(&cx);
4727            cx.update(|cx| {
4728                let mut options = (app_state.build_window_options)(None, cx);
4729                options.bounds = window_bounds_override;
4730                cx.open_window(options, |cx| {
4731                    cx.new_view(|cx| {
4732                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4733                    })
4734                })
4735            })?
4736        };
4737
4738        workspace.update(&mut cx, |workspace, cx| {
4739            cx.activate(true);
4740            cx.activate_window();
4741
4742            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4743                let follow_peer_id = room
4744                    .read(cx)
4745                    .remote_participants()
4746                    .iter()
4747                    .find(|(_, participant)| participant.user.id == follow_user_id)
4748                    .map(|(_, p)| p.peer_id)
4749                    .or_else(|| {
4750                        // If we couldn't follow the given user, follow the host instead.
4751                        let collaborator = workspace
4752                            .project()
4753                            .read(cx)
4754                            .collaborators()
4755                            .values()
4756                            .find(|collaborator| collaborator.replica_id == 0)?;
4757                        Some(collaborator.peer_id)
4758                    });
4759
4760                if let Some(follow_peer_id) = follow_peer_id {
4761                    workspace.follow(follow_peer_id, cx);
4762                }
4763            }
4764        })?;
4765
4766        anyhow::Ok(())
4767    })
4768}
4769
4770pub fn restart(_: &Restart, cx: &mut AppContext) {
4771    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4772    let mut workspace_windows = cx
4773        .windows()
4774        .into_iter()
4775        .filter_map(|window| window.downcast::<Workspace>())
4776        .collect::<Vec<_>>();
4777
4778    // If multiple windows have unsaved changes, and need a save prompt,
4779    // prompt in the active window before switching to a different window.
4780    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4781
4782    let mut prompt = None;
4783    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4784        prompt = window
4785            .update(cx, |_, cx| {
4786                cx.prompt(
4787                    PromptLevel::Info,
4788                    "Are you sure you want to restart?",
4789                    None,
4790                    &["Restart", "Cancel"],
4791                )
4792            })
4793            .ok();
4794    }
4795
4796    cx.spawn(|mut cx| async move {
4797        if let Some(prompt) = prompt {
4798            let answer = prompt.await?;
4799            if answer != 0 {
4800                return Ok(());
4801            }
4802        }
4803
4804        // If the user cancels any save prompt, then keep the app open.
4805        for window in workspace_windows {
4806            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4807                workspace.prepare_to_close(true, cx)
4808            }) {
4809                if !should_close.await? {
4810                    return Ok(());
4811                }
4812            }
4813        }
4814
4815        cx.update(|cx| cx.restart())
4816    })
4817    .detach_and_log_err(cx);
4818}
4819
4820fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4821    let mut parts = value.split(',');
4822    let x: usize = parts.next()?.parse().ok()?;
4823    let y: usize = parts.next()?.parse().ok()?;
4824    Some(point((x as f64).into(), (y as f64).into()))
4825}
4826
4827fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4828    let mut parts = value.split(',');
4829    let width: usize = parts.next()?.parse().ok()?;
4830    let height: usize = parts.next()?.parse().ok()?;
4831    Some(size((width as f64).into(), (height as f64).into()))
4832}
4833
4834struct DisconnectedOverlay;
4835
4836impl Element for DisconnectedOverlay {
4837    type BeforeLayout = AnyElement;
4838    type AfterLayout = ();
4839
4840    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
4841        let mut background = cx.theme().colors().elevated_surface_background;
4842        background.fade_out(0.2);
4843        let mut overlay = div()
4844            .bg(background)
4845            .absolute()
4846            .left_0()
4847            .top(ui::TitleBar::height(cx))
4848            .size_full()
4849            .flex()
4850            .items_center()
4851            .justify_center()
4852            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4853            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4854            .child(Label::new(
4855                "Your connection to the remote project has been lost.",
4856            ))
4857            .into_any();
4858        (overlay.before_layout(cx), overlay)
4859    }
4860
4861    fn after_layout(
4862        &mut self,
4863        bounds: Bounds<Pixels>,
4864        overlay: &mut Self::BeforeLayout,
4865        cx: &mut ElementContext,
4866    ) {
4867        cx.insert_hitbox(bounds, true);
4868        overlay.after_layout(cx);
4869    }
4870
4871    fn paint(
4872        &mut self,
4873        _: Bounds<Pixels>,
4874        overlay: &mut Self::BeforeLayout,
4875        _: &mut Self::AfterLayout,
4876        cx: &mut ElementContext,
4877    ) {
4878        overlay.paint(cx)
4879    }
4880}
4881
4882impl IntoElement for DisconnectedOverlay {
4883    type Element = Self;
4884
4885    fn into_element(self) -> Self::Element {
4886        self
4887    }
4888}
4889
4890#[cfg(test)]
4891mod tests {
4892    use std::{cell::RefCell, rc::Rc};
4893
4894    use super::*;
4895    use crate::{
4896        dock::{test::TestPanel, PanelEvent},
4897        item::{
4898            test::{TestItem, TestProjectItem},
4899            ItemEvent,
4900        },
4901    };
4902    use fs::FakeFs;
4903    use gpui::{
4904        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
4905        VisualTestContext,
4906    };
4907    use project::{Project, ProjectEntryId};
4908    use serde_json::json;
4909    use settings::SettingsStore;
4910
4911    #[gpui::test]
4912    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4913        init_test(cx);
4914
4915        let fs = FakeFs::new(cx.executor());
4916        let project = Project::test(fs, [], cx).await;
4917        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4918
4919        // Adding an item with no ambiguity renders the tab without detail.
4920        let item1 = cx.new_view(|cx| {
4921            let mut item = TestItem::new(cx);
4922            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4923            item
4924        });
4925        workspace.update(cx, |workspace, cx| {
4926            workspace.add_item_to_active_pane(Box::new(item1.clone()), cx);
4927        });
4928        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4929
4930        // Adding an item that creates ambiguity increases the level of detail on
4931        // both tabs.
4932        let item2 = cx.new_view(|cx| {
4933            let mut item = TestItem::new(cx);
4934            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4935            item
4936        });
4937        workspace.update(cx, |workspace, cx| {
4938            workspace.add_item_to_active_pane(Box::new(item2.clone()), cx);
4939        });
4940        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4941        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4942
4943        // Adding an item that creates ambiguity increases the level of detail only
4944        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4945        // we stop at the highest detail available.
4946        let item3 = cx.new_view(|cx| {
4947            let mut item = TestItem::new(cx);
4948            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4949            item
4950        });
4951        workspace.update(cx, |workspace, cx| {
4952            workspace.add_item_to_active_pane(Box::new(item3.clone()), cx);
4953        });
4954        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4955        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4956        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4957    }
4958
4959    #[gpui::test]
4960    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4961        init_test(cx);
4962
4963        let fs = FakeFs::new(cx.executor());
4964        fs.insert_tree(
4965            "/root1",
4966            json!({
4967                "one.txt": "",
4968                "two.txt": "",
4969            }),
4970        )
4971        .await;
4972        fs.insert_tree(
4973            "/root2",
4974            json!({
4975                "three.txt": "",
4976            }),
4977        )
4978        .await;
4979
4980        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4981        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4982        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4983        let worktree_id = project.update(cx, |project, cx| {
4984            project.worktrees().next().unwrap().read(cx).id()
4985        });
4986
4987        let item1 = cx.new_view(|cx| {
4988            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4989        });
4990        let item2 = cx.new_view(|cx| {
4991            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4992        });
4993
4994        // Add an item to an empty pane
4995        workspace.update(cx, |workspace, cx| {
4996            workspace.add_item_to_active_pane(Box::new(item1), cx)
4997        });
4998        project.update(cx, |project, cx| {
4999            assert_eq!(
5000                project.active_entry(),
5001                project
5002                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5003                    .map(|e| e.id)
5004            );
5005        });
5006        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5007
5008        // Add a second item to a non-empty pane
5009        workspace.update(cx, |workspace, cx| {
5010            workspace.add_item_to_active_pane(Box::new(item2), cx)
5011        });
5012        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
5013        project.update(cx, |project, cx| {
5014            assert_eq!(
5015                project.active_entry(),
5016                project
5017                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
5018                    .map(|e| e.id)
5019            );
5020        });
5021
5022        // Close the active item
5023        pane.update(cx, |pane, cx| {
5024            pane.close_active_item(&Default::default(), cx).unwrap()
5025        })
5026        .await
5027        .unwrap();
5028        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5029        project.update(cx, |project, cx| {
5030            assert_eq!(
5031                project.active_entry(),
5032                project
5033                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5034                    .map(|e| e.id)
5035            );
5036        });
5037
5038        // Add a project folder
5039        project
5040            .update(cx, |project, cx| {
5041                project.find_or_create_local_worktree("/root2", true, cx)
5042            })
5043            .await
5044            .unwrap();
5045        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
5046
5047        // Remove a project folder
5048        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
5049        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
5050    }
5051
5052    #[gpui::test]
5053    async fn test_close_window(cx: &mut TestAppContext) {
5054        init_test(cx);
5055
5056        let fs = FakeFs::new(cx.executor());
5057        fs.insert_tree("/root", json!({ "one": "" })).await;
5058
5059        let project = Project::test(fs, ["root".as_ref()], cx).await;
5060        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5061
5062        // When there are no dirty items, there's nothing to do.
5063        let item1 = cx.new_view(|cx| TestItem::new(cx));
5064        workspace.update(cx, |w, cx| {
5065            w.add_item_to_active_pane(Box::new(item1.clone()), cx)
5066        });
5067        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5068        assert!(task.await.unwrap());
5069
5070        // When there are dirty untitled items, prompt to save each one. If the user
5071        // cancels any prompt, then abort.
5072        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
5073        let item3 = cx.new_view(|cx| {
5074            TestItem::new(cx)
5075                .with_dirty(true)
5076                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5077        });
5078        workspace.update(cx, |w, cx| {
5079            w.add_item_to_active_pane(Box::new(item2.clone()), cx);
5080            w.add_item_to_active_pane(Box::new(item3.clone()), cx);
5081        });
5082        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5083        cx.executor().run_until_parked();
5084        cx.simulate_prompt_answer(2); // cancel save all
5085        cx.executor().run_until_parked();
5086        cx.simulate_prompt_answer(2); // cancel save all
5087        cx.executor().run_until_parked();
5088        assert!(!cx.has_pending_prompt());
5089        assert!(!task.await.unwrap());
5090    }
5091
5092    #[gpui::test]
5093    async fn test_close_pane_items(cx: &mut TestAppContext) {
5094        init_test(cx);
5095
5096        let fs = FakeFs::new(cx.executor());
5097
5098        let project = Project::test(fs, None, cx).await;
5099        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5100
5101        let item1 = cx.new_view(|cx| {
5102            TestItem::new(cx)
5103                .with_dirty(true)
5104                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5105        });
5106        let item2 = cx.new_view(|cx| {
5107            TestItem::new(cx)
5108                .with_dirty(true)
5109                .with_conflict(true)
5110                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
5111        });
5112        let item3 = cx.new_view(|cx| {
5113            TestItem::new(cx)
5114                .with_dirty(true)
5115                .with_conflict(true)
5116                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
5117        });
5118        let item4 = cx.new_view(|cx| {
5119            TestItem::new(cx)
5120                .with_dirty(true)
5121                .with_project_items(&[TestProjectItem::new_untitled(cx)])
5122        });
5123        let pane = workspace.update(cx, |workspace, cx| {
5124            workspace.add_item_to_active_pane(Box::new(item1.clone()), cx);
5125            workspace.add_item_to_active_pane(Box::new(item2.clone()), cx);
5126            workspace.add_item_to_active_pane(Box::new(item3.clone()), cx);
5127            workspace.add_item_to_active_pane(Box::new(item4.clone()), cx);
5128            workspace.active_pane().clone()
5129        });
5130
5131        let close_items = pane.update(cx, |pane, cx| {
5132            pane.activate_item(1, true, true, cx);
5133            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5134            let item1_id = item1.item_id();
5135            let item3_id = item3.item_id();
5136            let item4_id = item4.item_id();
5137            pane.close_items(cx, SaveIntent::Close, move |id| {
5138                [item1_id, item3_id, item4_id].contains(&id)
5139            })
5140        });
5141        cx.executor().run_until_parked();
5142
5143        assert!(cx.has_pending_prompt());
5144        // Ignore "Save all" prompt
5145        cx.simulate_prompt_answer(2);
5146        cx.executor().run_until_parked();
5147        // There's a prompt to save item 1.
5148        pane.update(cx, |pane, _| {
5149            assert_eq!(pane.items_len(), 4);
5150            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
5151        });
5152        // Confirm saving item 1.
5153        cx.simulate_prompt_answer(0);
5154        cx.executor().run_until_parked();
5155
5156        // Item 1 is saved. There's a prompt to save item 3.
5157        pane.update(cx, |pane, cx| {
5158            assert_eq!(item1.read(cx).save_count, 1);
5159            assert_eq!(item1.read(cx).save_as_count, 0);
5160            assert_eq!(item1.read(cx).reload_count, 0);
5161            assert_eq!(pane.items_len(), 3);
5162            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
5163        });
5164        assert!(cx.has_pending_prompt());
5165
5166        // Cancel saving item 3.
5167        cx.simulate_prompt_answer(1);
5168        cx.executor().run_until_parked();
5169
5170        // Item 3 is reloaded. There's a prompt to save item 4.
5171        pane.update(cx, |pane, cx| {
5172            assert_eq!(item3.read(cx).save_count, 0);
5173            assert_eq!(item3.read(cx).save_as_count, 0);
5174            assert_eq!(item3.read(cx).reload_count, 1);
5175            assert_eq!(pane.items_len(), 2);
5176            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
5177        });
5178        assert!(cx.has_pending_prompt());
5179
5180        // Confirm saving item 4.
5181        cx.simulate_prompt_answer(0);
5182        cx.executor().run_until_parked();
5183
5184        // There's a prompt for a path for item 4.
5185        cx.simulate_new_path_selection(|_| Some(Default::default()));
5186        close_items.await.unwrap();
5187
5188        // The requested items are closed.
5189        pane.update(cx, |pane, cx| {
5190            assert_eq!(item4.read(cx).save_count, 0);
5191            assert_eq!(item4.read(cx).save_as_count, 1);
5192            assert_eq!(item4.read(cx).reload_count, 0);
5193            assert_eq!(pane.items_len(), 1);
5194            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5195        });
5196    }
5197
5198    #[gpui::test]
5199    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
5200        init_test(cx);
5201
5202        let fs = FakeFs::new(cx.executor());
5203        let project = Project::test(fs, [], cx).await;
5204        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5205
5206        // Create several workspace items with single project entries, and two
5207        // workspace items with multiple project entries.
5208        let single_entry_items = (0..=4)
5209            .map(|project_entry_id| {
5210                cx.new_view(|cx| {
5211                    TestItem::new(cx)
5212                        .with_dirty(true)
5213                        .with_project_items(&[TestProjectItem::new(
5214                            project_entry_id,
5215                            &format!("{project_entry_id}.txt"),
5216                            cx,
5217                        )])
5218                })
5219            })
5220            .collect::<Vec<_>>();
5221        let item_2_3 = cx.new_view(|cx| {
5222            TestItem::new(cx)
5223                .with_dirty(true)
5224                .with_singleton(false)
5225                .with_project_items(&[
5226                    single_entry_items[2].read(cx).project_items[0].clone(),
5227                    single_entry_items[3].read(cx).project_items[0].clone(),
5228                ])
5229        });
5230        let item_3_4 = cx.new_view(|cx| {
5231            TestItem::new(cx)
5232                .with_dirty(true)
5233                .with_singleton(false)
5234                .with_project_items(&[
5235                    single_entry_items[3].read(cx).project_items[0].clone(),
5236                    single_entry_items[4].read(cx).project_items[0].clone(),
5237                ])
5238        });
5239
5240        // Create two panes that contain the following project entries:
5241        //   left pane:
5242        //     multi-entry items:   (2, 3)
5243        //     single-entry items:  0, 1, 2, 3, 4
5244        //   right pane:
5245        //     single-entry items:  1
5246        //     multi-entry items:   (3, 4)
5247        let left_pane = workspace.update(cx, |workspace, cx| {
5248            let left_pane = workspace.active_pane().clone();
5249            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), cx);
5250            for item in single_entry_items {
5251                workspace.add_item_to_active_pane(Box::new(item), cx);
5252            }
5253            left_pane.update(cx, |pane, cx| {
5254                pane.activate_item(2, true, true, cx);
5255            });
5256
5257            let right_pane = workspace
5258                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5259                .unwrap();
5260
5261            right_pane.update(cx, |pane, cx| {
5262                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5263            });
5264
5265            left_pane
5266        });
5267
5268        cx.focus_view(&left_pane);
5269
5270        // When closing all of the items in the left pane, we should be prompted twice:
5271        // once for project entry 0, and once for project entry 2. Project entries 1,
5272        // 3, and 4 are all still open in the other paten. After those two
5273        // prompts, the task should complete.
5274
5275        let close = left_pane.update(cx, |pane, cx| {
5276            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5277        });
5278        cx.executor().run_until_parked();
5279
5280        // Discard "Save all" prompt
5281        cx.simulate_prompt_answer(2);
5282
5283        cx.executor().run_until_parked();
5284        left_pane.update(cx, |pane, cx| {
5285            assert_eq!(
5286                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5287                &[ProjectEntryId::from_proto(0)]
5288            );
5289        });
5290        cx.simulate_prompt_answer(0);
5291
5292        cx.executor().run_until_parked();
5293        left_pane.update(cx, |pane, cx| {
5294            assert_eq!(
5295                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5296                &[ProjectEntryId::from_proto(2)]
5297            );
5298        });
5299        cx.simulate_prompt_answer(0);
5300
5301        cx.executor().run_until_parked();
5302        close.await.unwrap();
5303        left_pane.update(cx, |pane, _| {
5304            assert_eq!(pane.items_len(), 0);
5305        });
5306    }
5307
5308    #[gpui::test]
5309    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5310        init_test(cx);
5311
5312        let fs = FakeFs::new(cx.executor());
5313        let project = Project::test(fs, [], cx).await;
5314        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5315        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5316
5317        let item = cx.new_view(|cx| {
5318            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5319        });
5320        let item_id = item.entity_id();
5321        workspace.update(cx, |workspace, cx| {
5322            workspace.add_item_to_active_pane(Box::new(item.clone()), cx);
5323        });
5324
5325        // Autosave on window change.
5326        item.update(cx, |item, cx| {
5327            cx.update_global(|settings: &mut SettingsStore, cx| {
5328                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5329                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5330                })
5331            });
5332            item.is_dirty = true;
5333        });
5334
5335        // Deactivating the window saves the file.
5336        cx.deactivate_window();
5337        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5338
5339        // Re-activating the window doesn't save the file.
5340        cx.update(|cx| cx.activate_window());
5341        cx.executor().run_until_parked();
5342        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5343
5344        // Autosave on focus change.
5345        item.update(cx, |item, cx| {
5346            cx.focus_self();
5347            cx.update_global(|settings: &mut SettingsStore, cx| {
5348                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5349                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5350                })
5351            });
5352            item.is_dirty = true;
5353        });
5354
5355        // Blurring the item saves the file.
5356        item.update(cx, |_, cx| cx.blur());
5357        cx.executor().run_until_parked();
5358        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5359
5360        // Deactivating the window still saves the file.
5361        item.update(cx, |item, cx| {
5362            cx.focus_self();
5363            item.is_dirty = true;
5364        });
5365        cx.deactivate_window();
5366        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5367
5368        // Autosave after delay.
5369        item.update(cx, |item, cx| {
5370            cx.update_global(|settings: &mut SettingsStore, cx| {
5371                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5372                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5373                })
5374            });
5375            item.is_dirty = true;
5376            cx.emit(ItemEvent::Edit);
5377        });
5378
5379        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5380        cx.executor().advance_clock(Duration::from_millis(250));
5381        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5382
5383        // After delay expires, the file is saved.
5384        cx.executor().advance_clock(Duration::from_millis(250));
5385        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5386
5387        // Autosave on focus change, ensuring closing the tab counts as such.
5388        item.update(cx, |item, cx| {
5389            cx.update_global(|settings: &mut SettingsStore, cx| {
5390                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5391                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5392                })
5393            });
5394            item.is_dirty = true;
5395        });
5396
5397        pane.update(cx, |pane, cx| {
5398            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5399        })
5400        .await
5401        .unwrap();
5402        assert!(!cx.has_pending_prompt());
5403        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5404
5405        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5406        workspace.update(cx, |workspace, cx| {
5407            workspace.add_item_to_active_pane(Box::new(item.clone()), cx);
5408        });
5409        item.update(cx, |item, cx| {
5410            item.project_items[0].update(cx, |item, _| {
5411                item.entry_id = None;
5412            });
5413            item.is_dirty = true;
5414            cx.blur();
5415        });
5416        cx.run_until_parked();
5417        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5418
5419        // Ensure autosave is prevented for deleted files also when closing the buffer.
5420        let _close_items = pane.update(cx, |pane, cx| {
5421            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5422        });
5423        cx.run_until_parked();
5424        assert!(cx.has_pending_prompt());
5425        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5426    }
5427
5428    #[gpui::test]
5429    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5430        init_test(cx);
5431
5432        let fs = FakeFs::new(cx.executor());
5433
5434        let project = Project::test(fs, [], cx).await;
5435        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5436
5437        let item = cx.new_view(|cx| {
5438            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5439        });
5440        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5441        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5442        let toolbar_notify_count = Rc::new(RefCell::new(0));
5443
5444        workspace.update(cx, |workspace, cx| {
5445            workspace.add_item_to_active_pane(Box::new(item.clone()), cx);
5446            let toolbar_notification_count = toolbar_notify_count.clone();
5447            cx.observe(&toolbar, move |_, _, _| {
5448                *toolbar_notification_count.borrow_mut() += 1
5449            })
5450            .detach();
5451        });
5452
5453        pane.update(cx, |pane, _| {
5454            assert!(!pane.can_navigate_backward());
5455            assert!(!pane.can_navigate_forward());
5456        });
5457
5458        item.update(cx, |item, cx| {
5459            item.set_state("one".to_string(), cx);
5460        });
5461
5462        // Toolbar must be notified to re-render the navigation buttons
5463        assert_eq!(*toolbar_notify_count.borrow(), 1);
5464
5465        pane.update(cx, |pane, _| {
5466            assert!(pane.can_navigate_backward());
5467            assert!(!pane.can_navigate_forward());
5468        });
5469
5470        workspace
5471            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5472            .await
5473            .unwrap();
5474
5475        assert_eq!(*toolbar_notify_count.borrow(), 2);
5476        pane.update(cx, |pane, _| {
5477            assert!(!pane.can_navigate_backward());
5478            assert!(pane.can_navigate_forward());
5479        });
5480    }
5481
5482    #[gpui::test]
5483    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5484        init_test(cx);
5485        let fs = FakeFs::new(cx.executor());
5486
5487        let project = Project::test(fs, [], cx).await;
5488        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5489
5490        let panel = workspace.update(cx, |workspace, cx| {
5491            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5492            workspace.add_panel(panel.clone(), cx);
5493
5494            workspace
5495                .right_dock()
5496                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5497
5498            panel
5499        });
5500
5501        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5502        pane.update(cx, |pane, cx| {
5503            let item = cx.new_view(|cx| TestItem::new(cx));
5504            pane.add_item(Box::new(item), true, true, None, cx);
5505        });
5506
5507        // Transfer focus from center to panel
5508        workspace.update(cx, |workspace, cx| {
5509            workspace.toggle_panel_focus::<TestPanel>(cx);
5510        });
5511
5512        workspace.update(cx, |workspace, cx| {
5513            assert!(workspace.right_dock().read(cx).is_open());
5514            assert!(!panel.is_zoomed(cx));
5515            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5516        });
5517
5518        // Transfer focus from panel to center
5519        workspace.update(cx, |workspace, cx| {
5520            workspace.toggle_panel_focus::<TestPanel>(cx);
5521        });
5522
5523        workspace.update(cx, |workspace, cx| {
5524            assert!(workspace.right_dock().read(cx).is_open());
5525            assert!(!panel.is_zoomed(cx));
5526            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5527        });
5528
5529        // Close the dock
5530        workspace.update(cx, |workspace, cx| {
5531            workspace.toggle_dock(DockPosition::Right, cx);
5532        });
5533
5534        workspace.update(cx, |workspace, cx| {
5535            assert!(!workspace.right_dock().read(cx).is_open());
5536            assert!(!panel.is_zoomed(cx));
5537            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5538        });
5539
5540        // Open the dock
5541        workspace.update(cx, |workspace, cx| {
5542            workspace.toggle_dock(DockPosition::Right, cx);
5543        });
5544
5545        workspace.update(cx, |workspace, cx| {
5546            assert!(workspace.right_dock().read(cx).is_open());
5547            assert!(!panel.is_zoomed(cx));
5548            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5549        });
5550
5551        // Focus and zoom panel
5552        panel.update(cx, |panel, cx| {
5553            cx.focus_self();
5554            panel.set_zoomed(true, cx)
5555        });
5556
5557        workspace.update(cx, |workspace, cx| {
5558            assert!(workspace.right_dock().read(cx).is_open());
5559            assert!(panel.is_zoomed(cx));
5560            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5561        });
5562
5563        // Transfer focus to the center closes the dock
5564        workspace.update(cx, |workspace, cx| {
5565            workspace.toggle_panel_focus::<TestPanel>(cx);
5566        });
5567
5568        workspace.update(cx, |workspace, cx| {
5569            assert!(!workspace.right_dock().read(cx).is_open());
5570            assert!(panel.is_zoomed(cx));
5571            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5572        });
5573
5574        // Transferring focus back to the panel keeps it zoomed
5575        workspace.update(cx, |workspace, cx| {
5576            workspace.toggle_panel_focus::<TestPanel>(cx);
5577        });
5578
5579        workspace.update(cx, |workspace, cx| {
5580            assert!(workspace.right_dock().read(cx).is_open());
5581            assert!(panel.is_zoomed(cx));
5582            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5583        });
5584
5585        // Close the dock while it is zoomed
5586        workspace.update(cx, |workspace, cx| {
5587            workspace.toggle_dock(DockPosition::Right, cx)
5588        });
5589
5590        workspace.update(cx, |workspace, cx| {
5591            assert!(!workspace.right_dock().read(cx).is_open());
5592            assert!(panel.is_zoomed(cx));
5593            assert!(workspace.zoomed.is_none());
5594            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5595        });
5596
5597        // Opening the dock, when it's zoomed, retains focus
5598        workspace.update(cx, |workspace, cx| {
5599            workspace.toggle_dock(DockPosition::Right, cx)
5600        });
5601
5602        workspace.update(cx, |workspace, cx| {
5603            assert!(workspace.right_dock().read(cx).is_open());
5604            assert!(panel.is_zoomed(cx));
5605            assert!(workspace.zoomed.is_some());
5606            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5607        });
5608
5609        // Unzoom and close the panel, zoom the active pane.
5610        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5611        workspace.update(cx, |workspace, cx| {
5612            workspace.toggle_dock(DockPosition::Right, cx)
5613        });
5614        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5615
5616        // Opening a dock unzooms the pane.
5617        workspace.update(cx, |workspace, cx| {
5618            workspace.toggle_dock(DockPosition::Right, cx)
5619        });
5620        workspace.update(cx, |workspace, cx| {
5621            let pane = pane.read(cx);
5622            assert!(!pane.is_zoomed());
5623            assert!(!pane.focus_handle(cx).is_focused(cx));
5624            assert!(workspace.right_dock().read(cx).is_open());
5625            assert!(workspace.zoomed.is_none());
5626        });
5627    }
5628
5629    struct TestModal(FocusHandle);
5630
5631    impl TestModal {
5632        fn new(cx: &mut ViewContext<Self>) -> Self {
5633            Self(cx.focus_handle())
5634        }
5635    }
5636
5637    impl EventEmitter<DismissEvent> for TestModal {}
5638
5639    impl FocusableView for TestModal {
5640        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5641            self.0.clone()
5642        }
5643    }
5644
5645    impl ModalView for TestModal {}
5646
5647    impl Render for TestModal {
5648        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5649            div().track_focus(&self.0)
5650        }
5651    }
5652
5653    #[gpui::test]
5654    async fn test_panels(cx: &mut gpui::TestAppContext) {
5655        init_test(cx);
5656        let fs = FakeFs::new(cx.executor());
5657
5658        let project = Project::test(fs, [], cx).await;
5659        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5660
5661        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5662            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5663            workspace.add_panel(panel_1.clone(), cx);
5664            workspace
5665                .left_dock()
5666                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5667            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5668            workspace.add_panel(panel_2.clone(), cx);
5669            workspace
5670                .right_dock()
5671                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5672
5673            let left_dock = workspace.left_dock();
5674            assert_eq!(
5675                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5676                panel_1.panel_id()
5677            );
5678            assert_eq!(
5679                left_dock.read(cx).active_panel_size(cx).unwrap(),
5680                panel_1.size(cx)
5681            );
5682
5683            left_dock.update(cx, |left_dock, cx| {
5684                left_dock.resize_active_panel(Some(px(1337.)), cx)
5685            });
5686            assert_eq!(
5687                workspace
5688                    .right_dock()
5689                    .read(cx)
5690                    .visible_panel()
5691                    .unwrap()
5692                    .panel_id(),
5693                panel_2.panel_id(),
5694            );
5695
5696            (panel_1, panel_2)
5697        });
5698
5699        // Move panel_1 to the right
5700        panel_1.update(cx, |panel_1, cx| {
5701            panel_1.set_position(DockPosition::Right, cx)
5702        });
5703
5704        workspace.update(cx, |workspace, cx| {
5705            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5706            // Since it was the only panel on the left, the left dock should now be closed.
5707            assert!(!workspace.left_dock().read(cx).is_open());
5708            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5709            let right_dock = workspace.right_dock();
5710            assert_eq!(
5711                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5712                panel_1.panel_id()
5713            );
5714            assert_eq!(
5715                right_dock.read(cx).active_panel_size(cx).unwrap(),
5716                px(1337.)
5717            );
5718
5719            // Now we move panel_2 to the left
5720            panel_2.set_position(DockPosition::Left, cx);
5721        });
5722
5723        workspace.update(cx, |workspace, cx| {
5724            // Since panel_2 was not visible on the right, we don't open the left dock.
5725            assert!(!workspace.left_dock().read(cx).is_open());
5726            // And the right dock is unaffected in its displaying of panel_1
5727            assert!(workspace.right_dock().read(cx).is_open());
5728            assert_eq!(
5729                workspace
5730                    .right_dock()
5731                    .read(cx)
5732                    .visible_panel()
5733                    .unwrap()
5734                    .panel_id(),
5735                panel_1.panel_id(),
5736            );
5737        });
5738
5739        // Move panel_1 back to the left
5740        panel_1.update(cx, |panel_1, cx| {
5741            panel_1.set_position(DockPosition::Left, cx)
5742        });
5743
5744        workspace.update(cx, |workspace, cx| {
5745            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5746            let left_dock = workspace.left_dock();
5747            assert!(left_dock.read(cx).is_open());
5748            assert_eq!(
5749                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5750                panel_1.panel_id()
5751            );
5752            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5753            // And the right dock should be closed as it no longer has any panels.
5754            assert!(!workspace.right_dock().read(cx).is_open());
5755
5756            // Now we move panel_1 to the bottom
5757            panel_1.set_position(DockPosition::Bottom, cx);
5758        });
5759
5760        workspace.update(cx, |workspace, cx| {
5761            // Since panel_1 was visible on the left, we close the left dock.
5762            assert!(!workspace.left_dock().read(cx).is_open());
5763            // The bottom dock is sized based on the panel's default size,
5764            // since the panel orientation changed from vertical to horizontal.
5765            let bottom_dock = workspace.bottom_dock();
5766            assert_eq!(
5767                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5768                panel_1.size(cx),
5769            );
5770            // Close bottom dock and move panel_1 back to the left.
5771            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5772            panel_1.set_position(DockPosition::Left, cx);
5773        });
5774
5775        // Emit activated event on panel 1
5776        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5777
5778        // Now the left dock is open and panel_1 is active and focused.
5779        workspace.update(cx, |workspace, cx| {
5780            let left_dock = workspace.left_dock();
5781            assert!(left_dock.read(cx).is_open());
5782            assert_eq!(
5783                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5784                panel_1.panel_id(),
5785            );
5786            assert!(panel_1.focus_handle(cx).is_focused(cx));
5787        });
5788
5789        // Emit closed event on panel 2, which is not active
5790        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5791
5792        // Wo don't close the left dock, because panel_2 wasn't the active panel
5793        workspace.update(cx, |workspace, cx| {
5794            let left_dock = workspace.left_dock();
5795            assert!(left_dock.read(cx).is_open());
5796            assert_eq!(
5797                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5798                panel_1.panel_id(),
5799            );
5800        });
5801
5802        // Emitting a ZoomIn event shows the panel as zoomed.
5803        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5804        workspace.update(cx, |workspace, _| {
5805            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5806            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5807        });
5808
5809        // Move panel to another dock while it is zoomed
5810        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5811        workspace.update(cx, |workspace, _| {
5812            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5813
5814            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5815        });
5816
5817        // This is a helper for getting a:
5818        // - valid focus on an element,
5819        // - that isn't a part of the panes and panels system of the Workspace,
5820        // - and doesn't trigger the 'on_focus_lost' API.
5821        let focus_other_view = {
5822            let workspace = workspace.clone();
5823            move |cx: &mut VisualTestContext| {
5824                workspace.update(cx, |workspace, cx| {
5825                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5826                        workspace.toggle_modal(cx, TestModal::new);
5827                        workspace.toggle_modal(cx, TestModal::new);
5828                    } else {
5829                        workspace.toggle_modal(cx, TestModal::new);
5830                    }
5831                })
5832            }
5833        };
5834
5835        // If focus is transferred to another view that's not a panel or another pane, we still show
5836        // the panel as zoomed.
5837        focus_other_view(cx);
5838        workspace.update(cx, |workspace, _| {
5839            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5840            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5841        });
5842
5843        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5844        workspace.update(cx, |_, cx| cx.focus_self());
5845        workspace.update(cx, |workspace, _| {
5846            assert_eq!(workspace.zoomed, None);
5847            assert_eq!(workspace.zoomed_position, None);
5848        });
5849
5850        // If focus is transferred again to another view that's not a panel or a pane, we won't
5851        // show the panel as zoomed because it wasn't zoomed before.
5852        focus_other_view(cx);
5853        workspace.update(cx, |workspace, _| {
5854            assert_eq!(workspace.zoomed, None);
5855            assert_eq!(workspace.zoomed_position, None);
5856        });
5857
5858        // When the panel is activated, it is zoomed again.
5859        cx.dispatch_action(ToggleRightDock);
5860        workspace.update(cx, |workspace, _| {
5861            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5862            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5863        });
5864
5865        // Emitting a ZoomOut event unzooms the panel.
5866        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5867        workspace.update(cx, |workspace, _| {
5868            assert_eq!(workspace.zoomed, None);
5869            assert_eq!(workspace.zoomed_position, None);
5870        });
5871
5872        // Emit closed event on panel 1, which is active
5873        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5874
5875        // Now the left dock is closed, because panel_1 was the active panel
5876        workspace.update(cx, |workspace, cx| {
5877            let right_dock = workspace.right_dock();
5878            assert!(!right_dock.read(cx).is_open());
5879        });
5880    }
5881
5882    mod register_project_item_tests {
5883        use ui::Context as _;
5884
5885        use super::*;
5886
5887        const TEST_PNG_KIND: &str = "TestPngItemView";
5888        // View
5889        struct TestPngItemView {
5890            focus_handle: FocusHandle,
5891        }
5892        // Model
5893        struct TestPngItem {}
5894
5895        impl project::Item for TestPngItem {
5896            fn try_open(
5897                _project: &Model<Project>,
5898                path: &ProjectPath,
5899                cx: &mut AppContext,
5900            ) -> Option<Task<gpui::Result<Model<Self>>>> {
5901                if path.path.extension().unwrap() == "png" {
5902                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
5903                } else {
5904                    None
5905                }
5906            }
5907
5908            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
5909                None
5910            }
5911
5912            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
5913                None
5914            }
5915        }
5916
5917        impl Item for TestPngItemView {
5918            type Event = ();
5919
5920            fn serialized_item_kind() -> Option<&'static str> {
5921                Some(TEST_PNG_KIND)
5922            }
5923        }
5924        impl EventEmitter<()> for TestPngItemView {}
5925        impl FocusableView for TestPngItemView {
5926            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5927                self.focus_handle.clone()
5928            }
5929        }
5930
5931        impl Render for TestPngItemView {
5932            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
5933                Empty
5934            }
5935        }
5936
5937        impl ProjectItem for TestPngItemView {
5938            type Item = TestPngItem;
5939
5940            fn for_project_item(
5941                _project: Model<Project>,
5942                _item: Model<Self::Item>,
5943                cx: &mut ViewContext<Self>,
5944            ) -> Self
5945            where
5946                Self: Sized,
5947            {
5948                Self {
5949                    focus_handle: cx.focus_handle(),
5950                }
5951            }
5952        }
5953
5954        const TEST_IPYNB_KIND: &str = "TestIpynbItemView";
5955        // View
5956        struct TestIpynbItemView {
5957            focus_handle: FocusHandle,
5958        }
5959        // Model
5960        struct TestIpynbItem {}
5961
5962        impl project::Item for TestIpynbItem {
5963            fn try_open(
5964                _project: &Model<Project>,
5965                path: &ProjectPath,
5966                cx: &mut AppContext,
5967            ) -> Option<Task<gpui::Result<Model<Self>>>> {
5968                if path.path.extension().unwrap() == "ipynb" {
5969                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
5970                } else {
5971                    None
5972                }
5973            }
5974
5975            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
5976                None
5977            }
5978
5979            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
5980                None
5981            }
5982        }
5983
5984        impl Item for TestIpynbItemView {
5985            type Event = ();
5986
5987            fn serialized_item_kind() -> Option<&'static str> {
5988                Some(TEST_IPYNB_KIND)
5989            }
5990        }
5991        impl EventEmitter<()> for TestIpynbItemView {}
5992        impl FocusableView for TestIpynbItemView {
5993            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5994                self.focus_handle.clone()
5995            }
5996        }
5997
5998        impl Render for TestIpynbItemView {
5999            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6000                Empty
6001            }
6002        }
6003
6004        impl ProjectItem for TestIpynbItemView {
6005            type Item = TestIpynbItem;
6006
6007            fn for_project_item(
6008                _project: Model<Project>,
6009                _item: Model<Self::Item>,
6010                cx: &mut ViewContext<Self>,
6011            ) -> Self
6012            where
6013                Self: Sized,
6014            {
6015                Self {
6016                    focus_handle: cx.focus_handle(),
6017                }
6018            }
6019        }
6020
6021        struct TestAlternatePngItemView {
6022            focus_handle: FocusHandle,
6023        }
6024
6025        const TEST_ALTERNATE_PNG_KIND: &str = "TestAlternatePngItemView";
6026        impl Item for TestAlternatePngItemView {
6027            type Event = ();
6028
6029            fn serialized_item_kind() -> Option<&'static str> {
6030                Some(TEST_ALTERNATE_PNG_KIND)
6031            }
6032        }
6033        impl EventEmitter<()> for TestAlternatePngItemView {}
6034        impl FocusableView for TestAlternatePngItemView {
6035            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6036                self.focus_handle.clone()
6037            }
6038        }
6039
6040        impl Render for TestAlternatePngItemView {
6041            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6042                Empty
6043            }
6044        }
6045
6046        impl ProjectItem for TestAlternatePngItemView {
6047            type Item = TestPngItem;
6048
6049            fn for_project_item(
6050                _project: Model<Project>,
6051                _item: Model<Self::Item>,
6052                cx: &mut ViewContext<Self>,
6053            ) -> Self
6054            where
6055                Self: Sized,
6056            {
6057                Self {
6058                    focus_handle: cx.focus_handle(),
6059                }
6060            }
6061        }
6062
6063        #[gpui::test]
6064        async fn test_register_project_item(cx: &mut TestAppContext) {
6065            init_test(cx);
6066
6067            cx.update(|cx| {
6068                register_project_item::<TestPngItemView>(cx);
6069                register_project_item::<TestIpynbItemView>(cx);
6070            });
6071
6072            let fs = FakeFs::new(cx.executor());
6073            fs.insert_tree(
6074                "/root1",
6075                json!({
6076                    "one.png": "BINARYDATAHERE",
6077                    "two.ipynb": "{ totally a notebook }",
6078                    "three.txt": "editing text, sure why not?"
6079                }),
6080            )
6081            .await;
6082
6083            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6084            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6085
6086            let worktree_id = project.update(cx, |project, cx| {
6087                project.worktrees().next().unwrap().read(cx).id()
6088            });
6089
6090            let handle = workspace
6091                .update(cx, |workspace, cx| {
6092                    let project_path = (worktree_id, "one.png");
6093                    workspace.open_path(project_path, None, true, cx)
6094                })
6095                .await
6096                .unwrap();
6097
6098            // Now we can check if the handle we got back errored or not
6099            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_PNG_KIND);
6100
6101            let handle = workspace
6102                .update(cx, |workspace, cx| {
6103                    let project_path = (worktree_id, "two.ipynb");
6104                    workspace.open_path(project_path, None, true, cx)
6105                })
6106                .await
6107                .unwrap();
6108
6109            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_IPYNB_KIND);
6110
6111            let handle = workspace
6112                .update(cx, |workspace, cx| {
6113                    let project_path = (worktree_id, "three.txt");
6114                    workspace.open_path(project_path, None, true, cx)
6115                })
6116                .await;
6117            assert!(handle.is_err());
6118        }
6119
6120        #[gpui::test]
6121        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
6122            init_test(cx);
6123
6124            cx.update(|cx| {
6125                register_project_item::<TestPngItemView>(cx);
6126                register_project_item::<TestAlternatePngItemView>(cx);
6127            });
6128
6129            let fs = FakeFs::new(cx.executor());
6130            fs.insert_tree(
6131                "/root1",
6132                json!({
6133                    "one.png": "BINARYDATAHERE",
6134                    "two.ipynb": "{ totally a notebook }",
6135                    "three.txt": "editing text, sure why not?"
6136                }),
6137            )
6138            .await;
6139
6140            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6141            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6142
6143            let worktree_id = project.update(cx, |project, cx| {
6144                project.worktrees().next().unwrap().read(cx).id()
6145            });
6146
6147            let handle = workspace
6148                .update(cx, |workspace, cx| {
6149                    let project_path = (worktree_id, "one.png");
6150                    workspace.open_path(project_path, None, true, cx)
6151                })
6152                .await
6153                .unwrap();
6154
6155            // This _must_ be the second item registered
6156            assert_eq!(
6157                handle.serialized_item_kind().unwrap(),
6158                TEST_ALTERNATE_PNG_KIND
6159            );
6160
6161            let handle = workspace
6162                .update(cx, |workspace, cx| {
6163                    let project_path = (worktree_id, "three.txt");
6164                    workspace.open_path(project_path, None, true, cx)
6165                })
6166                .await;
6167            assert!(handle.is_err());
6168        }
6169    }
6170
6171    pub fn init_test(cx: &mut TestAppContext) {
6172        cx.update(|cx| {
6173            let settings_store = SettingsStore::test(cx);
6174            cx.set_global(settings_store);
6175            theme::init(theme::LoadThemes::JustBase, cx);
6176            language::init(cx);
6177            crate::init_settings(cx);
6178            Project::init_settings(cx);
6179        });
6180    }
6181}