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