workspace.rs

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