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