workspace.rs

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