workspace.rs

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