workspace.rs

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