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