workspace.rs

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