workspace.rs

   1pub mod dock;
   2pub mod item;
   3mod modal_layer;
   4pub mod notifications;
   5pub mod pane;
   6pub mod pane_group;
   7mod persistence;
   8pub mod searchable;
   9pub mod shared_screen;
  10mod status_bar;
  11mod toolbar;
  12mod workspace_settings;
  13
  14use anyhow::{anyhow, Context as _, Result};
  15use call::{call_settings::CallSettings, ActiveCall};
  16use client::{
  17    proto::{self, ErrorCode, PeerId},
  18    ChannelId, Client, ErrorExt, ProjectId, Status, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use derive_more::{Deref, DerefMut};
  22use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  23use futures::{
  24    channel::{mpsc, oneshot},
  25    future::try_join_all,
  26    Future, FutureExt, StreamExt,
  27};
  28use gpui::{
  29    actions, canvas, impl_actions, point, relative, size, Action, AnyElement, AnyView, AnyWeakView,
  30    AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DevicePixels, DragMoveEvent,
  31    Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global, KeyContext, Keystroke,
  32    LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render,
  33    Size, Subscription, Task, View, WeakView, WindowHandle, WindowOptions,
  34};
  35use item::{
  36    FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
  37    ProjectItem,
  38};
  39use itertools::Itertools;
  40use language::{LanguageRegistry, Rope};
  41use lazy_static::lazy_static;
  42pub use modal_layer::*;
  43use node_runtime::NodeRuntime;
  44use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  45pub use pane::*;
  46pub use pane_group::*;
  47use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB};
  48pub use persistence::{
  49    model::{ItemId, WorkspaceLocation},
  50    WorkspaceDb, DB as WORKSPACE_DB,
  51};
  52use postage::stream::Stream;
  53use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  54use serde::Deserialize;
  55use settings::Settings;
  56use shared_screen::SharedScreen;
  57use sqlez::{
  58    bindable::{Bind, Column, StaticColumnCount},
  59    statement::Statement,
  60};
  61use status_bar::StatusBar;
  62pub use status_bar::StatusItemView;
  63use std::{
  64    any::TypeId,
  65    borrow::Cow,
  66    cell::RefCell,
  67    cmp,
  68    collections::hash_map::DefaultHasher,
  69    env,
  70    hash::{Hash, Hasher},
  71    path::{Path, PathBuf},
  72    rc::Rc,
  73    sync::{atomic::AtomicUsize, Arc, Weak},
  74    time::Duration,
  75};
  76use task::SpawnInTerminal;
  77use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
  78pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  79pub use ui;
  80use ui::{
  81    div, h_flex, Context as _, Div, Element, ElementContext, FluentBuilder,
  82    InteractiveElement as _, IntoElement, Label, ParentElement as _, Pixels, SharedString,
  83    Styled as _, ViewContext, VisualContext as _, WindowContext,
  84};
  85use util::ResultExt;
  86use uuid::Uuid;
  87pub use workspace_settings::{
  88    AutosaveSetting, RestoreOnStartupBehaviour, TabBarSettings, WorkspaceSettings,
  89};
  90
  91use crate::notifications::NotificationId;
  92use crate::persistence::{
  93    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  94    SerializedAxis,
  95};
  96
  97lazy_static! {
  98    static ref ZED_WINDOW_SIZE: Option<Size<DevicePixels>> = env::var("ZED_WINDOW_SIZE")
  99        .ok()
 100        .as_deref()
 101        .and_then(parse_pixel_size_env_var);
 102    static ref ZED_WINDOW_POSITION: Option<Point<DevicePixels>> = env::var("ZED_WINDOW_POSITION")
 103        .ok()
 104        .as_deref()
 105        .and_then(parse_pixel_position_env_var);
 106}
 107
 108#[derive(Clone, PartialEq)]
 109pub struct RemoveWorktreeFromProject(pub WorktreeId);
 110
 111actions!(
 112    workspace,
 113    [
 114        Open,
 115        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 location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3396        let project = self.project().read(cx);
3397
3398        if project.is_local() {
3399            Some(
3400                project
3401                    .visible_worktrees(cx)
3402                    .map(|worktree| worktree.read(cx).abs_path())
3403                    .collect::<Vec<_>>()
3404                    .into(),
3405            )
3406        } else {
3407            None
3408        }
3409    }
3410
3411    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3412        match member {
3413            Member::Axis(PaneAxis { members, .. }) => {
3414                for child in members.iter() {
3415                    self.remove_panes(child.clone(), cx)
3416                }
3417            }
3418            Member::Pane(pane) => {
3419                self.force_remove_pane(&pane, cx);
3420            }
3421        }
3422    }
3423
3424    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3425        self.panes.retain(|p| p != pane);
3426        self.panes
3427            .last()
3428            .unwrap()
3429            .update(cx, |pane, cx| pane.focus(cx));
3430        if self.last_active_center_pane == Some(pane.downgrade()) {
3431            self.last_active_center_pane = None;
3432        }
3433        cx.notify();
3434    }
3435
3436    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3437        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3438            cx.background_executor()
3439                .timer(Duration::from_millis(100))
3440                .await;
3441            this.update(&mut cx, |this, cx| this.serialize_workspace(cx).detach())
3442                .log_err();
3443        }));
3444    }
3445
3446    fn serialize_workspace(&self, cx: &mut WindowContext) -> Task<()> {
3447        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3448            let (items, active) = {
3449                let pane = pane_handle.read(cx);
3450                let active_item_id = pane.active_item().map(|item| item.item_id());
3451                (
3452                    pane.items()
3453                        .filter_map(|item_handle| {
3454                            Some(SerializedItem {
3455                                kind: Arc::from(item_handle.serialized_item_kind()?),
3456                                item_id: item_handle.item_id().as_u64(),
3457                                active: Some(item_handle.item_id()) == active_item_id,
3458                                preview: pane.is_active_preview_item(item_handle.item_id()),
3459                            })
3460                        })
3461                        .collect::<Vec<_>>(),
3462                    pane.has_focus(cx),
3463                )
3464            };
3465
3466            SerializedPane::new(items, active)
3467        }
3468
3469        fn build_serialized_pane_group(
3470            pane_group: &Member,
3471            cx: &WindowContext,
3472        ) -> SerializedPaneGroup {
3473            match pane_group {
3474                Member::Axis(PaneAxis {
3475                    axis,
3476                    members,
3477                    flexes,
3478                    bounding_boxes: _,
3479                }) => SerializedPaneGroup::Group {
3480                    axis: SerializedAxis(*axis),
3481                    children: members
3482                        .iter()
3483                        .map(|member| build_serialized_pane_group(member, cx))
3484                        .collect::<Vec<_>>(),
3485                    flexes: Some(flexes.lock().clone()),
3486                },
3487                Member::Pane(pane_handle) => {
3488                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3489                }
3490            }
3491        }
3492
3493        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3494            let left_dock = this.left_dock.read(cx);
3495            let left_visible = left_dock.is_open();
3496            let left_active_panel = left_dock
3497                .visible_panel()
3498                .map(|panel| panel.persistent_name().to_string());
3499            let left_dock_zoom = left_dock
3500                .visible_panel()
3501                .map(|panel| panel.is_zoomed(cx))
3502                .unwrap_or(false);
3503
3504            let right_dock = this.right_dock.read(cx);
3505            let right_visible = right_dock.is_open();
3506            let right_active_panel = right_dock
3507                .visible_panel()
3508                .map(|panel| panel.persistent_name().to_string());
3509            let right_dock_zoom = right_dock
3510                .visible_panel()
3511                .map(|panel| panel.is_zoomed(cx))
3512                .unwrap_or(false);
3513
3514            let bottom_dock = this.bottom_dock.read(cx);
3515            let bottom_visible = bottom_dock.is_open();
3516            let bottom_active_panel = bottom_dock
3517                .visible_panel()
3518                .map(|panel| panel.persistent_name().to_string());
3519            let bottom_dock_zoom = bottom_dock
3520                .visible_panel()
3521                .map(|panel| panel.is_zoomed(cx))
3522                .unwrap_or(false);
3523
3524            DockStructure {
3525                left: DockData {
3526                    visible: left_visible,
3527                    active_panel: left_active_panel,
3528                    zoom: left_dock_zoom,
3529                },
3530                right: DockData {
3531                    visible: right_visible,
3532                    active_panel: right_active_panel,
3533                    zoom: right_dock_zoom,
3534                },
3535                bottom: DockData {
3536                    visible: bottom_visible,
3537                    active_panel: bottom_active_panel,
3538                    zoom: bottom_dock_zoom,
3539                },
3540            }
3541        }
3542
3543        if let Some(location) = self.location(cx) {
3544            // Load bearing special case:
3545            //  - with_local_workspace() relies on this to not have other stuff open
3546            //    when you open your log
3547            if !location.paths().is_empty() {
3548                let center_group = build_serialized_pane_group(&self.center.root, cx);
3549                let docks = build_serialized_docks(self, cx);
3550                let serialized_workspace = SerializedWorkspace {
3551                    id: self.database_id,
3552                    location,
3553                    center_group,
3554                    bounds: Default::default(),
3555                    display: Default::default(),
3556                    docks,
3557                    fullscreen: cx.is_fullscreen(),
3558                    centered_layout: self.centered_layout,
3559                };
3560                return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
3561            }
3562        }
3563        Task::ready(())
3564    }
3565
3566    pub(crate) fn load_workspace(
3567        serialized_workspace: SerializedWorkspace,
3568        paths_to_open: Vec<Option<ProjectPath>>,
3569        cx: &mut ViewContext<Workspace>,
3570    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3571        cx.spawn(|workspace, mut cx| async move {
3572            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3573
3574            let mut center_group = None;
3575            let mut center_items = None;
3576
3577            // Traverse the splits tree and add to things
3578            if let Some((group, active_pane, items)) = serialized_workspace
3579                .center_group
3580                .deserialize(
3581                    &project,
3582                    serialized_workspace.id,
3583                    workspace.clone(),
3584                    &mut cx,
3585                )
3586                .await
3587            {
3588                center_items = Some(items);
3589                center_group = Some((group, active_pane))
3590            }
3591
3592            let mut items_by_project_path = cx.update(|cx| {
3593                center_items
3594                    .unwrap_or_default()
3595                    .into_iter()
3596                    .filter_map(|item| {
3597                        let item = item?;
3598                        let project_path = item.project_path(cx)?;
3599                        Some((project_path, item))
3600                    })
3601                    .collect::<HashMap<_, _>>()
3602            })?;
3603
3604            let opened_items = paths_to_open
3605                .into_iter()
3606                .map(|path_to_open| {
3607                    path_to_open
3608                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3609                })
3610                .collect::<Vec<_>>();
3611
3612            // Remove old panes from workspace panes list
3613            workspace.update(&mut cx, |workspace, cx| {
3614                if let Some((center_group, active_pane)) = center_group {
3615                    workspace.remove_panes(workspace.center.root.clone(), cx);
3616
3617                    // Swap workspace center group
3618                    workspace.center = PaneGroup::with_root(center_group);
3619                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3620                    if let Some(active_pane) = active_pane {
3621                        workspace.active_pane = active_pane;
3622                        cx.focus_self();
3623                    } else {
3624                        workspace.active_pane = workspace.center.first_pane().clone();
3625                    }
3626                }
3627
3628                let docks = serialized_workspace.docks;
3629
3630                let right = docks.right.clone();
3631                workspace
3632                    .right_dock
3633                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3634                let left = docks.left.clone();
3635                workspace
3636                    .left_dock
3637                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3638                let bottom = docks.bottom.clone();
3639                workspace
3640                    .bottom_dock
3641                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3642
3643                cx.notify();
3644            })?;
3645
3646            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3647            workspace.update(&mut cx, |workspace, cx| {
3648                workspace.serialize_workspace(cx).detach()
3649            })?;
3650
3651            Ok(opened_items)
3652        })
3653    }
3654
3655    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3656        self.add_workspace_actions_listeners(div, cx)
3657            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3658            .on_action(cx.listener(Self::close_all_items_and_panes))
3659            .on_action(cx.listener(Self::save_all))
3660            .on_action(cx.listener(Self::send_keystrokes))
3661            .on_action(cx.listener(Self::add_folder_to_project))
3662            .on_action(cx.listener(Self::follow_next_collaborator))
3663            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3664                let pane = workspace.active_pane().clone();
3665                workspace.unfollow(&pane, cx);
3666            }))
3667            .on_action(cx.listener(|workspace, action: &Save, cx| {
3668                workspace
3669                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3670                    .detach_and_log_err(cx);
3671            }))
3672            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
3673                workspace
3674                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
3675                    .detach_and_log_err(cx);
3676            }))
3677            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3678                workspace
3679                    .save_active_item(SaveIntent::SaveAs, cx)
3680                    .detach_and_log_err(cx);
3681            }))
3682            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3683                workspace.activate_previous_pane(cx)
3684            }))
3685            .on_action(
3686                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3687            )
3688            .on_action(
3689                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3690                    workspace.activate_pane_in_direction(action.0, cx)
3691                }),
3692            )
3693            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3694                workspace.swap_pane_in_direction(action.0, cx)
3695            }))
3696            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3697                this.toggle_dock(DockPosition::Left, cx);
3698            }))
3699            .on_action(
3700                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3701                    workspace.toggle_dock(DockPosition::Right, cx);
3702                }),
3703            )
3704            .on_action(
3705                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3706                    workspace.toggle_dock(DockPosition::Bottom, cx);
3707                }),
3708            )
3709            .on_action(
3710                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3711                    workspace.close_all_docks(cx);
3712                }),
3713            )
3714            .on_action(cx.listener(Workspace::open))
3715            .on_action(cx.listener(Workspace::close_window))
3716            .on_action(cx.listener(Workspace::activate_pane_at_index))
3717            .on_action(
3718                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3719                    workspace.reopen_closed_item(cx).detach();
3720                }),
3721            )
3722            .on_action(cx.listener(Workspace::toggle_centered_layout))
3723    }
3724
3725    #[cfg(any(test, feature = "test-support"))]
3726    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3727        use node_runtime::FakeNodeRuntime;
3728
3729        let client = project.read(cx).client();
3730        let user_store = project.read(cx).user_store();
3731
3732        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3733        cx.activate_window();
3734        let app_state = Arc::new(AppState {
3735            languages: project.read(cx).languages().clone(),
3736            workspace_store,
3737            client,
3738            user_store,
3739            fs: project.read(cx).fs().clone(),
3740            build_window_options: |_, _| Default::default(),
3741            node_runtime: FakeNodeRuntime::new(),
3742        });
3743        let workspace = Self::new(Default::default(), project, app_state, cx);
3744        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3745        workspace
3746    }
3747
3748    pub fn register_action<A: Action>(
3749        &mut self,
3750        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3751    ) -> &mut Self {
3752        let callback = Arc::new(callback);
3753
3754        self.workspace_actions.push(Box::new(move |div, cx| {
3755            let callback = callback.clone();
3756            div.on_action(
3757                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3758            )
3759        }));
3760        self
3761    }
3762
3763    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3764        let mut div = div
3765            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3766            .on_action(cx.listener(Self::close_all_items_and_panes))
3767            .on_action(cx.listener(Self::add_folder_to_project))
3768            .on_action(cx.listener(Self::save_all))
3769            .on_action(cx.listener(Self::open));
3770        for action in self.workspace_actions.iter() {
3771            div = (action)(div, cx)
3772        }
3773        div
3774    }
3775
3776    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3777        self.modal_layer.read(cx).has_active_modal()
3778    }
3779
3780    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3781        self.modal_layer.read(cx).active_modal()
3782    }
3783
3784    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3785    where
3786        B: FnOnce(&mut ViewContext<V>) -> V,
3787    {
3788        self.modal_layer
3789            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3790    }
3791
3792    pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
3793        self.centered_layout = !self.centered_layout;
3794        cx.background_executor()
3795            .spawn(DB.set_centered_layout(self.database_id, self.centered_layout))
3796            .detach_and_log_err(cx);
3797        cx.notify();
3798    }
3799
3800    fn adjust_padding(padding: Option<f32>) -> f32 {
3801        padding
3802            .unwrap_or(Self::DEFAULT_PADDING)
3803            .min(Self::MAX_PADDING)
3804            .max(0.0)
3805    }
3806}
3807
3808fn window_bounds_env_override() -> Option<Bounds<DevicePixels>> {
3809    ZED_WINDOW_POSITION
3810        .zip(*ZED_WINDOW_SIZE)
3811        .map(|(position, size)| Bounds {
3812            origin: position,
3813            size,
3814        })
3815}
3816
3817fn open_items(
3818    serialized_workspace: Option<SerializedWorkspace>,
3819    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3820    app_state: Arc<AppState>,
3821    cx: &mut ViewContext<Workspace>,
3822) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3823    let restored_items = serialized_workspace.map(|serialized_workspace| {
3824        Workspace::load_workspace(
3825            serialized_workspace,
3826            project_paths_to_open
3827                .iter()
3828                .map(|(_, project_path)| project_path)
3829                .cloned()
3830                .collect(),
3831            cx,
3832        )
3833    });
3834
3835    cx.spawn(|workspace, mut cx| async move {
3836        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3837
3838        if let Some(restored_items) = restored_items {
3839            let restored_items = restored_items.await?;
3840
3841            let restored_project_paths = restored_items
3842                .iter()
3843                .filter_map(|item| {
3844                    cx.update(|cx| item.as_ref()?.project_path(cx))
3845                        .ok()
3846                        .flatten()
3847                })
3848                .collect::<HashSet<_>>();
3849
3850            for restored_item in restored_items {
3851                opened_items.push(restored_item.map(Ok));
3852            }
3853
3854            project_paths_to_open
3855                .iter_mut()
3856                .for_each(|(_, project_path)| {
3857                    if let Some(project_path_to_open) = project_path {
3858                        if restored_project_paths.contains(project_path_to_open) {
3859                            *project_path = None;
3860                        }
3861                    }
3862                });
3863        } else {
3864            for _ in 0..project_paths_to_open.len() {
3865                opened_items.push(None);
3866            }
3867        }
3868        assert!(opened_items.len() == project_paths_to_open.len());
3869
3870        let tasks =
3871            project_paths_to_open
3872                .into_iter()
3873                .enumerate()
3874                .map(|(ix, (abs_path, project_path))| {
3875                    let workspace = workspace.clone();
3876                    cx.spawn(|mut cx| {
3877                        let fs = app_state.fs.clone();
3878                        async move {
3879                            let file_project_path = project_path?;
3880                            if fs.is_dir(&abs_path).await {
3881                                None
3882                            } else {
3883                                Some((
3884                                    ix,
3885                                    workspace
3886                                        .update(&mut cx, |workspace, cx| {
3887                                            workspace.open_path(file_project_path, None, true, cx)
3888                                        })
3889                                        .log_err()?
3890                                        .await,
3891                                ))
3892                            }
3893                        }
3894                    })
3895                });
3896
3897        let tasks = tasks.collect::<Vec<_>>();
3898
3899        let tasks = futures::future::join_all(tasks);
3900        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
3901            opened_items[ix] = Some(path_open_result);
3902        }
3903
3904        Ok(opened_items)
3905    })
3906}
3907
3908enum ActivateInDirectionTarget {
3909    Pane(View<Pane>),
3910    Dock(View<Dock>),
3911}
3912
3913fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3914    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3915
3916    workspace
3917        .update(cx, |workspace, cx| {
3918            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3919                struct DatabaseFailedNotification;
3920
3921                workspace.show_notification_once(
3922                    NotificationId::unique::<DatabaseFailedNotification>(),
3923                    cx,
3924                    |cx| {
3925                        cx.new_view(|_| {
3926                            MessageNotification::new("Failed to load the database file.")
3927                                .with_click_message("Click to let us know about this error")
3928                                .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3929                        })
3930                    },
3931                );
3932            }
3933        })
3934        .log_err();
3935}
3936
3937impl FocusableView for Workspace {
3938    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3939        self.active_pane.focus_handle(cx)
3940    }
3941}
3942
3943#[derive(Clone, Render)]
3944struct DraggedDock(DockPosition);
3945
3946impl Render for Workspace {
3947    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3948        let mut context = KeyContext::new_with_defaults();
3949        context.add("Workspace");
3950        let centered_layout = self.centered_layout
3951            && self.center.panes().len() == 1
3952            && self.active_item(cx).is_some();
3953        let render_padding = |size| {
3954            (size > 0.0).then(|| {
3955                div()
3956                    .h_full()
3957                    .w(relative(size))
3958                    .bg(cx.theme().colors().editor_background)
3959                    .border_color(cx.theme().colors().pane_group_border)
3960            })
3961        };
3962        let paddings = if centered_layout {
3963            let settings = WorkspaceSettings::get_global(cx).centered_layout;
3964            (
3965                render_padding(Self::adjust_padding(settings.left_padding)),
3966                render_padding(Self::adjust_padding(settings.right_padding)),
3967            )
3968        } else {
3969            (None, None)
3970        };
3971        let (ui_font, ui_font_size) = {
3972            let theme_settings = ThemeSettings::get_global(cx);
3973            (
3974                theme_settings.ui_font.family.clone(),
3975                theme_settings.ui_font_size,
3976            )
3977        };
3978
3979        let theme = cx.theme().clone();
3980        let colors = theme.colors();
3981        cx.set_rem_size(ui_font_size);
3982
3983        self.actions(div(), cx)
3984            .key_context(context)
3985            .relative()
3986            .size_full()
3987            .flex()
3988            .flex_col()
3989            .font(ui_font)
3990            .gap_0()
3991            .justify_start()
3992            .items_start()
3993            .text_color(colors.text)
3994            .bg(colors.background)
3995            .children(self.titlebar_item.clone())
3996            .child(
3997                div()
3998                    .id("workspace")
3999                    .relative()
4000                    .flex_1()
4001                    .w_full()
4002                    .flex()
4003                    .flex_col()
4004                    .overflow_hidden()
4005                    .border_t()
4006                    .border_b()
4007                    .border_color(colors.border)
4008                    .child({
4009                        let this = cx.view().clone();
4010                        canvas(
4011                            move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
4012                            |_, _, _| {},
4013                        )
4014                        .absolute()
4015                        .size_full()
4016                    })
4017                    .when(self.zoomed.is_none(), |this| {
4018                        this.on_drag_move(cx.listener(
4019                            |workspace, e: &DragMoveEvent<DraggedDock>, cx| match e.drag(cx).0 {
4020                                DockPosition::Left => {
4021                                    let size = workspace.bounds.left() + e.event.position.x;
4022                                    workspace.left_dock.update(cx, |left_dock, cx| {
4023                                        left_dock.resize_active_panel(Some(size), cx);
4024                                    });
4025                                }
4026                                DockPosition::Right => {
4027                                    let size = workspace.bounds.right() - e.event.position.x;
4028                                    workspace.right_dock.update(cx, |right_dock, cx| {
4029                                        right_dock.resize_active_panel(Some(size), cx);
4030                                    });
4031                                }
4032                                DockPosition::Bottom => {
4033                                    let size = workspace.bounds.bottom() - e.event.position.y;
4034                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
4035                                        bottom_dock.resize_active_panel(Some(size), cx);
4036                                    });
4037                                }
4038                            },
4039                        ))
4040                    })
4041                    .child(
4042                        div()
4043                            .flex()
4044                            .flex_row()
4045                            .h_full()
4046                            // Left Dock
4047                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
4048                                || {
4049                                    div()
4050                                        .flex()
4051                                        .flex_none()
4052                                        .overflow_hidden()
4053                                        .child(self.left_dock.clone())
4054                                },
4055                            ))
4056                            // Panes
4057                            .child(
4058                                div()
4059                                    .flex()
4060                                    .flex_col()
4061                                    .flex_1()
4062                                    .overflow_hidden()
4063                                    .child(
4064                                        h_flex()
4065                                            .flex_1()
4066                                            .when_some(paddings.0, |this, p| {
4067                                                this.child(p.border_r_1())
4068                                            })
4069                                            .child(self.center.render(
4070                                                &self.project,
4071                                                &self.follower_states,
4072                                                self.active_call(),
4073                                                &self.active_pane,
4074                                                self.zoomed.as_ref(),
4075                                                &self.app_state,
4076                                                cx,
4077                                            ))
4078                                            .when_some(paddings.1, |this, p| {
4079                                                this.child(p.border_l_1())
4080                                            }),
4081                                    )
4082                                    .children(
4083                                        self.zoomed_position
4084                                            .ne(&Some(DockPosition::Bottom))
4085                                            .then(|| self.bottom_dock.clone()),
4086                                    ),
4087                            )
4088                            // Right Dock
4089                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
4090                                || {
4091                                    div()
4092                                        .flex()
4093                                        .flex_none()
4094                                        .overflow_hidden()
4095                                        .child(self.right_dock.clone())
4096                                },
4097                            )),
4098                    )
4099                    .children(self.zoomed.as_ref().and_then(|view| {
4100                        let zoomed_view = view.upgrade()?;
4101                        let div = div()
4102                            .occlude()
4103                            .absolute()
4104                            .overflow_hidden()
4105                            .border_color(colors.border)
4106                            .bg(colors.background)
4107                            .child(zoomed_view)
4108                            .inset_0()
4109                            .shadow_lg();
4110
4111                        Some(match self.zoomed_position {
4112                            Some(DockPosition::Left) => div.right_2().border_r(),
4113                            Some(DockPosition::Right) => div.left_2().border_l(),
4114                            Some(DockPosition::Bottom) => div.top_2().border_t(),
4115                            None => div.top_2().bottom_2().left_2().right_2().border(),
4116                        })
4117                    }))
4118                    .child(self.modal_layer.clone())
4119                    .children(self.render_notifications(cx)),
4120            )
4121            .child(self.status_bar.clone())
4122            .children(if self.project.read(cx).is_disconnected() {
4123                Some(DisconnectedOverlay)
4124            } else {
4125                None
4126            })
4127    }
4128}
4129
4130impl WorkspaceStore {
4131    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4132        Self {
4133            workspaces: Default::default(),
4134            _subscriptions: vec![
4135                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4136                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4137            ],
4138            client,
4139        }
4140    }
4141
4142    pub fn update_followers(
4143        &self,
4144        project_id: Option<u64>,
4145        update: proto::update_followers::Variant,
4146        cx: &AppContext,
4147    ) -> Option<()> {
4148        let active_call = ActiveCall::try_global(cx)?;
4149        let room_id = active_call.read(cx).room()?.read(cx).id();
4150        self.client
4151            .send(proto::UpdateFollowers {
4152                room_id,
4153                project_id,
4154                variant: Some(update),
4155            })
4156            .log_err()
4157    }
4158
4159    pub async fn handle_follow(
4160        this: Model<Self>,
4161        envelope: TypedEnvelope<proto::Follow>,
4162        _: Arc<Client>,
4163        mut cx: AsyncAppContext,
4164    ) -> Result<proto::FollowResponse> {
4165        this.update(&mut cx, |this, cx| {
4166            let follower = Follower {
4167                project_id: envelope.payload.project_id,
4168                peer_id: envelope.original_sender_id()?,
4169            };
4170
4171            let mut response = proto::FollowResponse::default();
4172            this.workspaces.retain(|workspace| {
4173                workspace
4174                    .update(cx, |workspace, cx| {
4175                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4176                        if response.views.is_empty() {
4177                            response.views = handler_response.views;
4178                        } else {
4179                            response.views.extend_from_slice(&handler_response.views);
4180                        }
4181
4182                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
4183                            if response.active_view_id.is_none()
4184                                || workspace.project.read(cx).remote_id() == follower.project_id
4185                            {
4186                                response.active_view_id = Some(active_view_id);
4187                            }
4188                        }
4189
4190                        if let Some(active_view) = handler_response.active_view.clone() {
4191                            if response.active_view_id.is_none()
4192                                || workspace.project.read(cx).remote_id() == follower.project_id
4193                            {
4194                                response.active_view = Some(active_view)
4195                            }
4196                        }
4197                    })
4198                    .is_ok()
4199            });
4200
4201            Ok(response)
4202        })?
4203    }
4204
4205    async fn handle_update_followers(
4206        this: Model<Self>,
4207        envelope: TypedEnvelope<proto::UpdateFollowers>,
4208        _: Arc<Client>,
4209        mut cx: AsyncAppContext,
4210    ) -> Result<()> {
4211        let leader_id = envelope.original_sender_id()?;
4212        let update = envelope.payload;
4213
4214        this.update(&mut cx, |this, cx| {
4215            this.workspaces.retain(|workspace| {
4216                workspace
4217                    .update(cx, |workspace, cx| {
4218                        let project_id = workspace.project.read(cx).remote_id();
4219                        if update.project_id != project_id && update.project_id.is_some() {
4220                            return;
4221                        }
4222                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4223                    })
4224                    .is_ok()
4225            });
4226            Ok(())
4227        })?
4228    }
4229}
4230
4231impl ViewId {
4232    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4233        Ok(Self {
4234            creator: message
4235                .creator
4236                .ok_or_else(|| anyhow!("creator is missing"))?,
4237            id: message.id,
4238        })
4239    }
4240
4241    pub(crate) fn to_proto(&self) -> proto::ViewId {
4242        proto::ViewId {
4243            creator: Some(self.creator),
4244            id: self.id,
4245        }
4246    }
4247}
4248
4249pub trait WorkspaceHandle {
4250    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4251}
4252
4253impl WorkspaceHandle for View<Workspace> {
4254    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4255        self.read(cx)
4256            .worktrees(cx)
4257            .flat_map(|worktree| {
4258                let worktree_id = worktree.read(cx).id();
4259                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4260                    worktree_id,
4261                    path: f.path.clone(),
4262                })
4263            })
4264            .collect::<Vec<_>>()
4265    }
4266}
4267
4268impl std::fmt::Debug for OpenPaths {
4269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4270        f.debug_struct("OpenPaths")
4271            .field("paths", &self.paths)
4272            .finish()
4273    }
4274}
4275
4276pub fn activate_workspace_for_project(
4277    cx: &mut AppContext,
4278    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4279) -> Option<WindowHandle<Workspace>> {
4280    for window in cx.windows() {
4281        let Some(workspace) = window.downcast::<Workspace>() else {
4282            continue;
4283        };
4284
4285        let predicate = workspace
4286            .update(cx, |workspace, cx| {
4287                let project = workspace.project.read(cx);
4288                if predicate(project, cx) {
4289                    cx.activate_window();
4290                    true
4291                } else {
4292                    false
4293                }
4294            })
4295            .log_err()
4296            .unwrap_or(false);
4297
4298        if predicate {
4299            return Some(workspace);
4300        }
4301    }
4302
4303    None
4304}
4305
4306pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4307    DB.last_workspace().await.log_err().flatten()
4308}
4309
4310actions!(collab, [OpenChannelNotes]);
4311actions!(zed, [OpenLog]);
4312
4313async fn join_channel_internal(
4314    channel_id: ChannelId,
4315    app_state: &Arc<AppState>,
4316    requesting_window: Option<WindowHandle<Workspace>>,
4317    active_call: &Model<ActiveCall>,
4318    cx: &mut AsyncAppContext,
4319) -> Result<bool> {
4320    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4321        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4322            return (false, None);
4323        };
4324
4325        let already_in_channel = room.channel_id() == Some(channel_id);
4326        let should_prompt = room.is_sharing_project()
4327            && room.remote_participants().len() > 0
4328            && !already_in_channel;
4329        let open_room = if already_in_channel {
4330            active_call.room().cloned()
4331        } else {
4332            None
4333        };
4334        (should_prompt, open_room)
4335    })?;
4336
4337    if let Some(room) = open_room {
4338        let task = room.update(cx, |room, cx| {
4339            if let Some((project, host)) = room.most_active_project(cx) {
4340                return Some(join_in_room_project(project, host, app_state.clone(), cx));
4341            }
4342
4343            None
4344        })?;
4345        if let Some(task) = task {
4346            task.await?;
4347        }
4348        return anyhow::Ok(true);
4349    }
4350
4351    if should_prompt {
4352        if let Some(workspace) = requesting_window {
4353            let answer = workspace
4354                .update(cx, |_, cx| {
4355                    cx.prompt(
4356                        PromptLevel::Warning,
4357                        "Do you want to switch channels?",
4358                        Some("Leaving this call will unshare your current project."),
4359                        &["Yes, Join Channel", "Cancel"],
4360                    )
4361                })?
4362                .await;
4363
4364            if answer == Ok(1) {
4365                return Ok(false);
4366            }
4367        } else {
4368            return Ok(false); // unreachable!() hopefully
4369        }
4370    }
4371
4372    let client = cx.update(|cx| active_call.read(cx).client())?;
4373
4374    let mut client_status = client.status();
4375
4376    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4377    'outer: loop {
4378        let Some(status) = client_status.recv().await else {
4379            return Err(anyhow!("error connecting"));
4380        };
4381
4382        match status {
4383            Status::Connecting
4384            | Status::Authenticating
4385            | Status::Reconnecting
4386            | Status::Reauthenticating => continue,
4387            Status::Connected { .. } => break 'outer,
4388            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4389            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4390            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4391                return Err(ErrorCode::Disconnected.into());
4392            }
4393        }
4394    }
4395
4396    let room = active_call
4397        .update(cx, |active_call, cx| {
4398            active_call.join_channel(channel_id, cx)
4399        })?
4400        .await?;
4401
4402    let Some(room) = room else {
4403        return anyhow::Ok(true);
4404    };
4405
4406    room.update(cx, |room, _| room.room_update_completed())?
4407        .await;
4408
4409    let task = room.update(cx, |room, cx| {
4410        if let Some((project, host)) = room.most_active_project(cx) {
4411            return Some(join_in_room_project(project, host, app_state.clone(), cx));
4412        }
4413
4414        // if you are the first to join a channel, share your project
4415        if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
4416            if let Some(workspace) = requesting_window {
4417                let project = workspace.update(cx, |workspace, cx| {
4418                    if !CallSettings::get_global(cx).share_on_join {
4419                        return None;
4420                    }
4421                    let project = workspace.project.read(cx);
4422                    if project.is_local()
4423                        && project.visible_worktrees(cx).any(|tree| {
4424                            tree.read(cx)
4425                                .root_entry()
4426                                .map_or(false, |entry| entry.is_dir())
4427                        })
4428                    {
4429                        Some(workspace.project.clone())
4430                    } else {
4431                        None
4432                    }
4433                });
4434                if let Ok(Some(project)) = project {
4435                    return Some(cx.spawn(|room, mut cx| async move {
4436                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4437                            .await?;
4438                        Ok(())
4439                    }));
4440                }
4441            }
4442        }
4443
4444        None
4445    })?;
4446    if let Some(task) = task {
4447        task.await?;
4448        return anyhow::Ok(true);
4449    }
4450    anyhow::Ok(false)
4451}
4452
4453pub fn join_channel(
4454    channel_id: ChannelId,
4455    app_state: Arc<AppState>,
4456    requesting_window: Option<WindowHandle<Workspace>>,
4457    cx: &mut AppContext,
4458) -> Task<Result<()>> {
4459    let active_call = ActiveCall::global(cx);
4460    cx.spawn(|mut cx| async move {
4461        let result = join_channel_internal(
4462            channel_id,
4463            &app_state,
4464            requesting_window,
4465            &active_call,
4466            &mut cx,
4467        )
4468            .await;
4469
4470        // join channel succeeded, and opened a window
4471        if matches!(result, Ok(true)) {
4472            return anyhow::Ok(());
4473        }
4474
4475        // find an existing workspace to focus and show call controls
4476        let mut active_window =
4477            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4478        if active_window.is_none() {
4479            // no open workspaces, make one to show the error in (blergh)
4480            let (window_handle, _) = cx
4481                .update(|cx| {
4482                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4483                })?
4484                .await?;
4485
4486            if result.is_ok() {
4487                cx.update(|cx| {
4488                    cx.dispatch_action(&OpenChannelNotes);
4489                }).log_err();
4490            }
4491
4492            active_window = Some(window_handle);
4493        }
4494
4495        if let Err(err) = result {
4496            log::error!("failed to join channel: {}", err);
4497            if let Some(active_window) = active_window {
4498                active_window
4499                    .update(&mut cx, |_, cx| {
4500                        let detail: SharedString = match err.error_code() {
4501                            ErrorCode::SignedOut => {
4502                                "Please sign in to continue.".into()
4503                            }
4504                            ErrorCode::UpgradeRequired => {
4505                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4506                            }
4507                            ErrorCode::NoSuchChannel => {
4508                                "No matching channel was found. Please check the link and try again.".into()
4509                            }
4510                            ErrorCode::Forbidden => {
4511                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4512                            }
4513                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4514                            _ => format!("{}\n\nPlease try again.", err).into(),
4515                        };
4516                        cx.prompt(
4517                            PromptLevel::Critical,
4518                            "Failed to join channel",
4519                            Some(&detail),
4520                            &["Ok"],
4521                        )
4522                    })?
4523                    .await
4524                    .ok();
4525            }
4526        }
4527
4528        // return ok, we showed the error to the user.
4529        return anyhow::Ok(());
4530    })
4531}
4532
4533pub async fn get_any_active_workspace(
4534    app_state: Arc<AppState>,
4535    mut cx: AsyncAppContext,
4536) -> anyhow::Result<WindowHandle<Workspace>> {
4537    // find an existing workspace to focus and show call controls
4538    let active_window = activate_any_workspace_window(&mut cx);
4539    if active_window.is_none() {
4540        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4541            .await?;
4542    }
4543    activate_any_workspace_window(&mut cx).context("could not open zed")
4544}
4545
4546fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4547    cx.update(|cx| {
4548        if let Some(workspace_window) = cx
4549            .active_window()
4550            .and_then(|window| window.downcast::<Workspace>())
4551        {
4552            return Some(workspace_window);
4553        }
4554
4555        for window in cx.windows() {
4556            if let Some(workspace_window) = window.downcast::<Workspace>() {
4557                workspace_window
4558                    .update(cx, |_, cx| cx.activate_window())
4559                    .ok();
4560                return Some(workspace_window);
4561            }
4562        }
4563        None
4564    })
4565    .ok()
4566    .flatten()
4567}
4568
4569fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
4570    cx.windows()
4571        .into_iter()
4572        .filter_map(|window| window.downcast::<Workspace>())
4573        .filter(|workspace| {
4574            workspace
4575                .read(cx)
4576                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
4577        })
4578        .collect()
4579}
4580
4581#[derive(Default)]
4582pub struct OpenOptions {
4583    pub open_new_workspace: Option<bool>,
4584    pub replace_window: Option<WindowHandle<Workspace>>,
4585}
4586
4587#[allow(clippy::type_complexity)]
4588pub fn open_paths(
4589    abs_paths: &[PathBuf],
4590    app_state: Arc<AppState>,
4591    open_options: OpenOptions,
4592    cx: &mut AppContext,
4593) -> Task<
4594    anyhow::Result<(
4595        WindowHandle<Workspace>,
4596        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4597    )>,
4598> {
4599    let abs_paths = abs_paths.to_vec();
4600    let mut existing = None;
4601    let mut best_match = None;
4602    let mut open_visible = OpenVisible::All;
4603
4604    if open_options.open_new_workspace != Some(true) {
4605        for window in local_workspace_windows(cx) {
4606            if let Ok(workspace) = window.read(cx) {
4607                let m = workspace
4608                    .project
4609                    .read(cx)
4610                    .visibility_for_paths(&abs_paths, cx);
4611                if m > best_match {
4612                    existing = Some(window);
4613                    best_match = m;
4614                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
4615                    existing = Some(window)
4616                }
4617            }
4618        }
4619    }
4620
4621    cx.spawn(move |mut cx| async move {
4622        if open_options.open_new_workspace.is_none() && existing.is_none() {
4623            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
4624            if futures::future::join_all(all_files)
4625                .await
4626                .into_iter()
4627                .filter_map(|result| result.ok().flatten())
4628                .all(|file| !file.is_dir)
4629            {
4630                cx.update(|cx| {
4631                    for window in local_workspace_windows(cx) {
4632                        if let Ok(workspace) = window.read(cx) {
4633                            let project = workspace.project().read(cx);
4634                            if project.is_remote() {
4635                                continue;
4636                            }
4637                            existing = Some(window);
4638                            open_visible = OpenVisible::None;
4639                            break;
4640                        }
4641                    }
4642                })?;
4643            }
4644        }
4645
4646        if let Some(existing) = existing {
4647            Ok((
4648                existing,
4649                existing
4650                    .update(&mut cx, |workspace, cx| {
4651                        cx.activate_window();
4652                        workspace.open_paths(abs_paths, open_visible, None, cx)
4653                    })?
4654                    .await,
4655            ))
4656        } else {
4657            cx.update(move |cx| {
4658                Workspace::new_local(
4659                    abs_paths,
4660                    app_state.clone(),
4661                    open_options.replace_window,
4662                    cx,
4663                )
4664            })?
4665            .await
4666        }
4667    })
4668}
4669
4670pub fn open_new(
4671    app_state: Arc<AppState>,
4672    cx: &mut AppContext,
4673    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4674) -> Task<()> {
4675    let task = Workspace::new_local(Vec::new(), app_state, None, cx);
4676    cx.spawn(|mut cx| async move {
4677        if let Some((workspace, opened_paths)) = task.await.log_err() {
4678            workspace
4679                .update(&mut cx, |workspace, cx| {
4680                    if opened_paths.is_empty() {
4681                        init(workspace, cx)
4682                    }
4683                })
4684                .log_err();
4685        }
4686    })
4687}
4688
4689pub fn create_and_open_local_file(
4690    path: &'static Path,
4691    cx: &mut ViewContext<Workspace>,
4692    default_content: impl 'static + Send + FnOnce() -> Rope,
4693) -> Task<Result<Box<dyn ItemHandle>>> {
4694    cx.spawn(|workspace, mut cx| async move {
4695        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4696        if !fs.is_file(path).await {
4697            fs.create_file(path, Default::default()).await?;
4698            fs.save(path, &default_content(), Default::default())
4699                .await?;
4700        }
4701
4702        let mut items = workspace
4703            .update(&mut cx, |workspace, cx| {
4704                workspace.with_local_workspace(cx, |workspace, cx| {
4705                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4706                })
4707            })?
4708            .await?
4709            .await;
4710
4711        let item = items.pop().flatten();
4712        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4713    })
4714}
4715
4716pub fn join_hosted_project(
4717    hosted_project_id: ProjectId,
4718    app_state: Arc<AppState>,
4719    cx: &mut AppContext,
4720) -> Task<Result<()>> {
4721    cx.spawn(|mut cx| async move {
4722        let existing_window = cx.update(|cx| {
4723            cx.windows().into_iter().find_map(|window| {
4724                let workspace = window.downcast::<Workspace>()?;
4725                workspace
4726                    .read(cx)
4727                    .is_ok_and(|workspace| {
4728                        workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
4729                    })
4730                    .then(|| workspace)
4731            })
4732        })?;
4733
4734        let workspace = if let Some(existing_window) = existing_window {
4735            existing_window
4736        } else {
4737            let project = Project::hosted(
4738                hosted_project_id,
4739                app_state.user_store.clone(),
4740                app_state.client.clone(),
4741                app_state.languages.clone(),
4742                app_state.fs.clone(),
4743                cx.clone(),
4744            )
4745            .await?;
4746
4747            let window_bounds_override = window_bounds_env_override();
4748            cx.update(|cx| {
4749                let mut options = (app_state.build_window_options)(None, cx);
4750                options.bounds = window_bounds_override;
4751                cx.open_window(options, |cx| {
4752                    cx.new_view(|cx| {
4753                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4754                    })
4755                })
4756            })?
4757        };
4758
4759        workspace.update(&mut cx, |_, cx| {
4760            cx.activate(true);
4761            cx.activate_window();
4762        })?;
4763
4764        Ok(())
4765    })
4766}
4767
4768pub fn join_remote_project(
4769    project_id: ProjectId,
4770    app_state: Arc<AppState>,
4771    cx: &mut AppContext,
4772) -> Task<Result<WindowHandle<Workspace>>> {
4773    let windows = cx.windows();
4774    cx.spawn(|mut cx| async move {
4775        let existing_workspace = windows.into_iter().find_map(|window| {
4776            window.downcast::<Workspace>().and_then(|window| {
4777                window
4778                    .update(&mut cx, |workspace, cx| {
4779                        if workspace.project().read(cx).remote_id() == Some(project_id.0) {
4780                            Some(window)
4781                        } else {
4782                            None
4783                        }
4784                    })
4785                    .unwrap_or(None)
4786            })
4787        });
4788
4789        let workspace = if let Some(existing_workspace) = existing_workspace {
4790            existing_workspace
4791        } else {
4792            let project = Project::remote(
4793                project_id.0,
4794                app_state.client.clone(),
4795                app_state.user_store.clone(),
4796                app_state.languages.clone(),
4797                app_state.fs.clone(),
4798                cx.clone(),
4799            )
4800            .await?;
4801
4802            let window_bounds_override = window_bounds_env_override();
4803            cx.update(|cx| {
4804                let mut options = (app_state.build_window_options)(None, cx);
4805                options.bounds = window_bounds_override;
4806                cx.open_window(options, |cx| {
4807                    cx.new_view(|cx| {
4808                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4809                    })
4810                })
4811            })?
4812        };
4813
4814        workspace.update(&mut cx, |_, cx| {
4815            cx.activate(true);
4816            cx.activate_window();
4817        })?;
4818
4819        anyhow::Ok(workspace)
4820    })
4821}
4822
4823pub fn join_in_room_project(
4824    project_id: u64,
4825    follow_user_id: u64,
4826    app_state: Arc<AppState>,
4827    cx: &mut AppContext,
4828) -> Task<Result<()>> {
4829    let windows = cx.windows();
4830    cx.spawn(|mut cx| async move {
4831        let existing_workspace = windows.into_iter().find_map(|window| {
4832            window.downcast::<Workspace>().and_then(|window| {
4833                window
4834                    .update(&mut cx, |workspace, cx| {
4835                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4836                            Some(window)
4837                        } else {
4838                            None
4839                        }
4840                    })
4841                    .unwrap_or(None)
4842            })
4843        });
4844
4845        let workspace = if let Some(existing_workspace) = existing_workspace {
4846            existing_workspace
4847        } else {
4848            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4849            let room = active_call
4850                .read_with(&cx, |call, _| call.room().cloned())?
4851                .ok_or_else(|| anyhow!("not in a call"))?;
4852            let project = room
4853                .update(&mut cx, |room, cx| {
4854                    room.join_project(
4855                        project_id,
4856                        app_state.languages.clone(),
4857                        app_state.fs.clone(),
4858                        cx,
4859                    )
4860                })?
4861                .await?;
4862
4863            let window_bounds_override = window_bounds_env_override();
4864            cx.update(|cx| {
4865                let mut options = (app_state.build_window_options)(None, cx);
4866                options.bounds = window_bounds_override;
4867                cx.open_window(options, |cx| {
4868                    cx.new_view(|cx| {
4869                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4870                    })
4871                })
4872            })?
4873        };
4874
4875        workspace.update(&mut cx, |workspace, cx| {
4876            cx.activate(true);
4877            cx.activate_window();
4878
4879            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4880                let follow_peer_id = room
4881                    .read(cx)
4882                    .remote_participants()
4883                    .iter()
4884                    .find(|(_, participant)| participant.user.id == follow_user_id)
4885                    .map(|(_, p)| p.peer_id)
4886                    .or_else(|| {
4887                        // If we couldn't follow the given user, follow the host instead.
4888                        let collaborator = workspace
4889                            .project()
4890                            .read(cx)
4891                            .collaborators()
4892                            .values()
4893                            .find(|collaborator| collaborator.replica_id == 0)?;
4894                        Some(collaborator.peer_id)
4895                    });
4896
4897                if let Some(follow_peer_id) = follow_peer_id {
4898                    workspace.follow(follow_peer_id, cx);
4899                }
4900            }
4901        })?;
4902
4903        anyhow::Ok(())
4904    })
4905}
4906
4907pub fn restart(_: &Restart, cx: &mut AppContext) {
4908    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4909    let mut workspace_windows = cx
4910        .windows()
4911        .into_iter()
4912        .filter_map(|window| window.downcast::<Workspace>())
4913        .collect::<Vec<_>>();
4914
4915    // If multiple windows have unsaved changes, and need a save prompt,
4916    // prompt in the active window before switching to a different window.
4917    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4918
4919    let mut prompt = None;
4920    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4921        prompt = window
4922            .update(cx, |_, cx| {
4923                cx.prompt(
4924                    PromptLevel::Info,
4925                    "Are you sure you want to restart?",
4926                    None,
4927                    &["Restart", "Cancel"],
4928                )
4929            })
4930            .ok();
4931    }
4932
4933    cx.spawn(|mut cx| async move {
4934        if let Some(prompt) = prompt {
4935            let answer = prompt.await?;
4936            if answer != 0 {
4937                return Ok(());
4938            }
4939        }
4940
4941        // If the user cancels any save prompt, then keep the app open.
4942        for window in workspace_windows {
4943            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4944                workspace.prepare_to_close(true, cx)
4945            }) {
4946                if !should_close.await? {
4947                    return Ok(());
4948                }
4949            }
4950        }
4951
4952        cx.update(|cx| cx.restart())
4953    })
4954    .detach_and_log_err(cx);
4955}
4956
4957fn parse_pixel_position_env_var(value: &str) -> Option<Point<DevicePixels>> {
4958    let mut parts = value.split(',');
4959    let x: usize = parts.next()?.parse().ok()?;
4960    let y: usize = parts.next()?.parse().ok()?;
4961    Some(point((x as i32).into(), (y as i32).into()))
4962}
4963
4964fn parse_pixel_size_env_var(value: &str) -> Option<Size<DevicePixels>> {
4965    let mut parts = value.split(',');
4966    let width: usize = parts.next()?.parse().ok()?;
4967    let height: usize = parts.next()?.parse().ok()?;
4968    Some(size((width as i32).into(), (height as i32).into()))
4969}
4970
4971struct DisconnectedOverlay;
4972
4973impl Element for DisconnectedOverlay {
4974    type RequestLayoutState = AnyElement;
4975    type PrepaintState = ();
4976
4977    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
4978        let mut background = cx.theme().colors().elevated_surface_background;
4979        background.fade_out(0.2);
4980        let mut overlay = div()
4981            .bg(background)
4982            .absolute()
4983            .left_0()
4984            .top(ui::TitleBar::height(cx))
4985            .size_full()
4986            .flex()
4987            .items_center()
4988            .justify_center()
4989            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4990            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4991            .child(Label::new(
4992                "Your connection to the remote project has been lost.",
4993            ))
4994            .into_any();
4995        (overlay.request_layout(cx), overlay)
4996    }
4997
4998    fn prepaint(
4999        &mut self,
5000        bounds: Bounds<Pixels>,
5001        overlay: &mut Self::RequestLayoutState,
5002        cx: &mut ElementContext,
5003    ) {
5004        cx.insert_hitbox(bounds, true);
5005        overlay.prepaint(cx);
5006    }
5007
5008    fn paint(
5009        &mut self,
5010        _: Bounds<Pixels>,
5011        overlay: &mut Self::RequestLayoutState,
5012        _: &mut Self::PrepaintState,
5013        cx: &mut ElementContext,
5014    ) {
5015        overlay.paint(cx)
5016    }
5017}
5018
5019impl IntoElement for DisconnectedOverlay {
5020    type Element = Self;
5021
5022    fn into_element(self) -> Self::Element {
5023        self
5024    }
5025}
5026
5027#[cfg(test)]
5028mod tests {
5029    use std::{cell::RefCell, rc::Rc};
5030
5031    use super::*;
5032    use crate::{
5033        dock::{test::TestPanel, PanelEvent},
5034        item::{
5035            test::{TestItem, TestProjectItem},
5036            ItemEvent,
5037        },
5038    };
5039    use fs::FakeFs;
5040    use gpui::{
5041        px, BorrowAppContext, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView,
5042        Render, TestAppContext, VisualTestContext,
5043    };
5044    use project::{Project, ProjectEntryId};
5045    use serde_json::json;
5046    use settings::SettingsStore;
5047
5048    #[gpui::test]
5049    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
5050        init_test(cx);
5051
5052        let fs = FakeFs::new(cx.executor());
5053        let project = Project::test(fs, [], cx).await;
5054        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5055
5056        // Adding an item with no ambiguity renders the tab without detail.
5057        let item1 = cx.new_view(|cx| {
5058            let mut item = TestItem::new(cx);
5059            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
5060            item
5061        });
5062        workspace.update(cx, |workspace, cx| {
5063            workspace.add_item_to_active_pane(Box::new(item1.clone()), cx);
5064        });
5065        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
5066
5067        // Adding an item that creates ambiguity increases the level of detail on
5068        // both tabs.
5069        let item2 = cx.new_view(|cx| {
5070            let mut item = TestItem::new(cx);
5071            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5072            item
5073        });
5074        workspace.update(cx, |workspace, cx| {
5075            workspace.add_item_to_active_pane(Box::new(item2.clone()), cx);
5076        });
5077        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5078        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5079
5080        // Adding an item that creates ambiguity increases the level of detail only
5081        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
5082        // we stop at the highest detail available.
5083        let item3 = cx.new_view(|cx| {
5084            let mut item = TestItem::new(cx);
5085            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5086            item
5087        });
5088        workspace.update(cx, |workspace, cx| {
5089            workspace.add_item_to_active_pane(Box::new(item3.clone()), cx);
5090        });
5091        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5092        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5093        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5094    }
5095
5096    #[gpui::test]
5097    async fn test_tracking_active_path(cx: &mut TestAppContext) {
5098        init_test(cx);
5099
5100        let fs = FakeFs::new(cx.executor());
5101        fs.insert_tree(
5102            "/root1",
5103            json!({
5104                "one.txt": "",
5105                "two.txt": "",
5106            }),
5107        )
5108        .await;
5109        fs.insert_tree(
5110            "/root2",
5111            json!({
5112                "three.txt": "",
5113            }),
5114        )
5115        .await;
5116
5117        let project = Project::test(fs, ["root1".as_ref()], cx).await;
5118        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5119        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5120        let worktree_id = project.update(cx, |project, cx| {
5121            project.worktrees().next().unwrap().read(cx).id()
5122        });
5123
5124        let item1 = cx.new_view(|cx| {
5125            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
5126        });
5127        let item2 = cx.new_view(|cx| {
5128            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
5129        });
5130
5131        // Add an item to an empty pane
5132        workspace.update(cx, |workspace, cx| {
5133            workspace.add_item_to_active_pane(Box::new(item1), cx)
5134        });
5135        project.update(cx, |project, cx| {
5136            assert_eq!(
5137                project.active_entry(),
5138                project
5139                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5140                    .map(|e| e.id)
5141            );
5142        });
5143        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5144
5145        // Add a second item to a non-empty pane
5146        workspace.update(cx, |workspace, cx| {
5147            workspace.add_item_to_active_pane(Box::new(item2), cx)
5148        });
5149        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
5150        project.update(cx, |project, cx| {
5151            assert_eq!(
5152                project.active_entry(),
5153                project
5154                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
5155                    .map(|e| e.id)
5156            );
5157        });
5158
5159        // Close the active item
5160        pane.update(cx, |pane, cx| {
5161            pane.close_active_item(&Default::default(), cx).unwrap()
5162        })
5163        .await
5164        .unwrap();
5165        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5166        project.update(cx, |project, cx| {
5167            assert_eq!(
5168                project.active_entry(),
5169                project
5170                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5171                    .map(|e| e.id)
5172            );
5173        });
5174
5175        // Add a project folder
5176        project
5177            .update(cx, |project, cx| {
5178                project.find_or_create_local_worktree("/root2", true, cx)
5179            })
5180            .await
5181            .unwrap();
5182        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
5183
5184        // Remove a project folder
5185        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
5186        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
5187    }
5188
5189    #[gpui::test]
5190    async fn test_close_window(cx: &mut TestAppContext) {
5191        init_test(cx);
5192
5193        let fs = FakeFs::new(cx.executor());
5194        fs.insert_tree("/root", json!({ "one": "" })).await;
5195
5196        let project = Project::test(fs, ["root".as_ref()], cx).await;
5197        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5198
5199        // When there are no dirty items, there's nothing to do.
5200        let item1 = cx.new_view(|cx| TestItem::new(cx));
5201        workspace.update(cx, |w, cx| {
5202            w.add_item_to_active_pane(Box::new(item1.clone()), cx)
5203        });
5204        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5205        assert!(task.await.unwrap());
5206
5207        // When there are dirty untitled items, prompt to save each one. If the user
5208        // cancels any prompt, then abort.
5209        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
5210        let item3 = cx.new_view(|cx| {
5211            TestItem::new(cx)
5212                .with_dirty(true)
5213                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5214        });
5215        workspace.update(cx, |w, cx| {
5216            w.add_item_to_active_pane(Box::new(item2.clone()), cx);
5217            w.add_item_to_active_pane(Box::new(item3.clone()), cx);
5218        });
5219        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5220        cx.executor().run_until_parked();
5221        cx.simulate_prompt_answer(2); // cancel save all
5222        cx.executor().run_until_parked();
5223        cx.simulate_prompt_answer(2); // cancel save all
5224        cx.executor().run_until_parked();
5225        assert!(!cx.has_pending_prompt());
5226        assert!(!task.await.unwrap());
5227    }
5228
5229    #[gpui::test]
5230    async fn test_close_pane_items(cx: &mut TestAppContext) {
5231        init_test(cx);
5232
5233        let fs = FakeFs::new(cx.executor());
5234
5235        let project = Project::test(fs, None, cx).await;
5236        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5237
5238        let item1 = cx.new_view(|cx| {
5239            TestItem::new(cx)
5240                .with_dirty(true)
5241                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5242        });
5243        let item2 = cx.new_view(|cx| {
5244            TestItem::new(cx)
5245                .with_dirty(true)
5246                .with_conflict(true)
5247                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
5248        });
5249        let item3 = cx.new_view(|cx| {
5250            TestItem::new(cx)
5251                .with_dirty(true)
5252                .with_conflict(true)
5253                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
5254        });
5255        let item4 = cx.new_view(|cx| {
5256            TestItem::new(cx)
5257                .with_dirty(true)
5258                .with_project_items(&[TestProjectItem::new_untitled(cx)])
5259        });
5260        let pane = workspace.update(cx, |workspace, cx| {
5261            workspace.add_item_to_active_pane(Box::new(item1.clone()), cx);
5262            workspace.add_item_to_active_pane(Box::new(item2.clone()), cx);
5263            workspace.add_item_to_active_pane(Box::new(item3.clone()), cx);
5264            workspace.add_item_to_active_pane(Box::new(item4.clone()), cx);
5265            workspace.active_pane().clone()
5266        });
5267
5268        let close_items = pane.update(cx, |pane, cx| {
5269            pane.activate_item(1, true, true, cx);
5270            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5271            let item1_id = item1.item_id();
5272            let item3_id = item3.item_id();
5273            let item4_id = item4.item_id();
5274            pane.close_items(cx, SaveIntent::Close, move |id| {
5275                [item1_id, item3_id, item4_id].contains(&id)
5276            })
5277        });
5278        cx.executor().run_until_parked();
5279
5280        assert!(cx.has_pending_prompt());
5281        // Ignore "Save all" prompt
5282        cx.simulate_prompt_answer(2);
5283        cx.executor().run_until_parked();
5284        // There's a prompt to save item 1.
5285        pane.update(cx, |pane, _| {
5286            assert_eq!(pane.items_len(), 4);
5287            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
5288        });
5289        // Confirm saving item 1.
5290        cx.simulate_prompt_answer(0);
5291        cx.executor().run_until_parked();
5292
5293        // Item 1 is saved. There's a prompt to save item 3.
5294        pane.update(cx, |pane, cx| {
5295            assert_eq!(item1.read(cx).save_count, 1);
5296            assert_eq!(item1.read(cx).save_as_count, 0);
5297            assert_eq!(item1.read(cx).reload_count, 0);
5298            assert_eq!(pane.items_len(), 3);
5299            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
5300        });
5301        assert!(cx.has_pending_prompt());
5302
5303        // Cancel saving item 3.
5304        cx.simulate_prompt_answer(1);
5305        cx.executor().run_until_parked();
5306
5307        // Item 3 is reloaded. There's a prompt to save item 4.
5308        pane.update(cx, |pane, cx| {
5309            assert_eq!(item3.read(cx).save_count, 0);
5310            assert_eq!(item3.read(cx).save_as_count, 0);
5311            assert_eq!(item3.read(cx).reload_count, 1);
5312            assert_eq!(pane.items_len(), 2);
5313            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
5314        });
5315        assert!(cx.has_pending_prompt());
5316
5317        // Confirm saving item 4.
5318        cx.simulate_prompt_answer(0);
5319        cx.executor().run_until_parked();
5320
5321        // There's a prompt for a path for item 4.
5322        cx.simulate_new_path_selection(|_| Some(Default::default()));
5323        close_items.await.unwrap();
5324
5325        // The requested items are closed.
5326        pane.update(cx, |pane, cx| {
5327            assert_eq!(item4.read(cx).save_count, 0);
5328            assert_eq!(item4.read(cx).save_as_count, 1);
5329            assert_eq!(item4.read(cx).reload_count, 0);
5330            assert_eq!(pane.items_len(), 1);
5331            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5332        });
5333    }
5334
5335    #[gpui::test]
5336    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
5337        init_test(cx);
5338
5339        let fs = FakeFs::new(cx.executor());
5340        let project = Project::test(fs, [], cx).await;
5341        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5342
5343        // Create several workspace items with single project entries, and two
5344        // workspace items with multiple project entries.
5345        let single_entry_items = (0..=4)
5346            .map(|project_entry_id| {
5347                cx.new_view(|cx| {
5348                    TestItem::new(cx)
5349                        .with_dirty(true)
5350                        .with_project_items(&[TestProjectItem::new(
5351                            project_entry_id,
5352                            &format!("{project_entry_id}.txt"),
5353                            cx,
5354                        )])
5355                })
5356            })
5357            .collect::<Vec<_>>();
5358        let item_2_3 = cx.new_view(|cx| {
5359            TestItem::new(cx)
5360                .with_dirty(true)
5361                .with_singleton(false)
5362                .with_project_items(&[
5363                    single_entry_items[2].read(cx).project_items[0].clone(),
5364                    single_entry_items[3].read(cx).project_items[0].clone(),
5365                ])
5366        });
5367        let item_3_4 = cx.new_view(|cx| {
5368            TestItem::new(cx)
5369                .with_dirty(true)
5370                .with_singleton(false)
5371                .with_project_items(&[
5372                    single_entry_items[3].read(cx).project_items[0].clone(),
5373                    single_entry_items[4].read(cx).project_items[0].clone(),
5374                ])
5375        });
5376
5377        // Create two panes that contain the following project entries:
5378        //   left pane:
5379        //     multi-entry items:   (2, 3)
5380        //     single-entry items:  0, 1, 2, 3, 4
5381        //   right pane:
5382        //     single-entry items:  1
5383        //     multi-entry items:   (3, 4)
5384        let left_pane = workspace.update(cx, |workspace, cx| {
5385            let left_pane = workspace.active_pane().clone();
5386            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), cx);
5387            for item in single_entry_items {
5388                workspace.add_item_to_active_pane(Box::new(item), cx);
5389            }
5390            left_pane.update(cx, |pane, cx| {
5391                pane.activate_item(2, true, true, cx);
5392            });
5393
5394            let right_pane = workspace
5395                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5396                .unwrap();
5397
5398            right_pane.update(cx, |pane, cx| {
5399                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5400            });
5401
5402            left_pane
5403        });
5404
5405        cx.focus_view(&left_pane);
5406
5407        // When closing all of the items in the left pane, we should be prompted twice:
5408        // once for project entry 0, and once for project entry 2. Project entries 1,
5409        // 3, and 4 are all still open in the other paten. After those two
5410        // prompts, the task should complete.
5411
5412        let close = left_pane.update(cx, |pane, cx| {
5413            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5414        });
5415        cx.executor().run_until_parked();
5416
5417        // Discard "Save all" prompt
5418        cx.simulate_prompt_answer(2);
5419
5420        cx.executor().run_until_parked();
5421        left_pane.update(cx, |pane, cx| {
5422            assert_eq!(
5423                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5424                &[ProjectEntryId::from_proto(0)]
5425            );
5426        });
5427        cx.simulate_prompt_answer(0);
5428
5429        cx.executor().run_until_parked();
5430        left_pane.update(cx, |pane, cx| {
5431            assert_eq!(
5432                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5433                &[ProjectEntryId::from_proto(2)]
5434            );
5435        });
5436        cx.simulate_prompt_answer(0);
5437
5438        cx.executor().run_until_parked();
5439        close.await.unwrap();
5440        left_pane.update(cx, |pane, _| {
5441            assert_eq!(pane.items_len(), 0);
5442        });
5443    }
5444
5445    #[gpui::test]
5446    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5447        init_test(cx);
5448
5449        let fs = FakeFs::new(cx.executor());
5450        let project = Project::test(fs, [], cx).await;
5451        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5452        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5453
5454        let item = cx.new_view(|cx| {
5455            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5456        });
5457        let item_id = item.entity_id();
5458        workspace.update(cx, |workspace, cx| {
5459            workspace.add_item_to_active_pane(Box::new(item.clone()), cx);
5460        });
5461
5462        // Autosave on window change.
5463        item.update(cx, |item, cx| {
5464            cx.update_global(|settings: &mut SettingsStore, cx| {
5465                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5466                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5467                })
5468            });
5469            item.is_dirty = true;
5470        });
5471
5472        // Deactivating the window saves the file.
5473        cx.deactivate_window();
5474        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5475
5476        // Re-activating the window doesn't save the file.
5477        cx.update(|cx| cx.activate_window());
5478        cx.executor().run_until_parked();
5479        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5480
5481        // Autosave on focus change.
5482        item.update(cx, |item, cx| {
5483            cx.focus_self();
5484            cx.update_global(|settings: &mut SettingsStore, cx| {
5485                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5486                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5487                })
5488            });
5489            item.is_dirty = true;
5490        });
5491
5492        // Blurring the item saves the file.
5493        item.update(cx, |_, cx| cx.blur());
5494        cx.executor().run_until_parked();
5495        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5496
5497        // Deactivating the window still saves the file.
5498        item.update(cx, |item, cx| {
5499            cx.focus_self();
5500            item.is_dirty = true;
5501        });
5502        cx.deactivate_window();
5503        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5504
5505        // Autosave after delay.
5506        item.update(cx, |item, cx| {
5507            cx.update_global(|settings: &mut SettingsStore, cx| {
5508                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5509                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5510                })
5511            });
5512            item.is_dirty = true;
5513            cx.emit(ItemEvent::Edit);
5514        });
5515
5516        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5517        cx.executor().advance_clock(Duration::from_millis(250));
5518        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5519
5520        // After delay expires, the file is saved.
5521        cx.executor().advance_clock(Duration::from_millis(250));
5522        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5523
5524        // Autosave on focus change, ensuring closing the tab counts as such.
5525        item.update(cx, |item, cx| {
5526            cx.update_global(|settings: &mut SettingsStore, cx| {
5527                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5528                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5529                })
5530            });
5531            item.is_dirty = true;
5532        });
5533
5534        pane.update(cx, |pane, cx| {
5535            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5536        })
5537        .await
5538        .unwrap();
5539        assert!(!cx.has_pending_prompt());
5540        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5541
5542        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5543        workspace.update(cx, |workspace, cx| {
5544            workspace.add_item_to_active_pane(Box::new(item.clone()), cx);
5545        });
5546        item.update(cx, |item, cx| {
5547            item.project_items[0].update(cx, |item, _| {
5548                item.entry_id = None;
5549            });
5550            item.is_dirty = true;
5551            cx.blur();
5552        });
5553        cx.run_until_parked();
5554        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5555
5556        // Ensure autosave is prevented for deleted files also when closing the buffer.
5557        let _close_items = pane.update(cx, |pane, cx| {
5558            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5559        });
5560        cx.run_until_parked();
5561        assert!(cx.has_pending_prompt());
5562        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5563    }
5564
5565    #[gpui::test]
5566    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5567        init_test(cx);
5568
5569        let fs = FakeFs::new(cx.executor());
5570
5571        let project = Project::test(fs, [], cx).await;
5572        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5573
5574        let item = cx.new_view(|cx| {
5575            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5576        });
5577        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5578        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5579        let toolbar_notify_count = Rc::new(RefCell::new(0));
5580
5581        workspace.update(cx, |workspace, cx| {
5582            workspace.add_item_to_active_pane(Box::new(item.clone()), cx);
5583            let toolbar_notification_count = toolbar_notify_count.clone();
5584            cx.observe(&toolbar, move |_, _, _| {
5585                *toolbar_notification_count.borrow_mut() += 1
5586            })
5587            .detach();
5588        });
5589
5590        pane.update(cx, |pane, _| {
5591            assert!(!pane.can_navigate_backward());
5592            assert!(!pane.can_navigate_forward());
5593        });
5594
5595        item.update(cx, |item, cx| {
5596            item.set_state("one".to_string(), cx);
5597        });
5598
5599        // Toolbar must be notified to re-render the navigation buttons
5600        assert_eq!(*toolbar_notify_count.borrow(), 1);
5601
5602        pane.update(cx, |pane, _| {
5603            assert!(pane.can_navigate_backward());
5604            assert!(!pane.can_navigate_forward());
5605        });
5606
5607        workspace
5608            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5609            .await
5610            .unwrap();
5611
5612        assert_eq!(*toolbar_notify_count.borrow(), 2);
5613        pane.update(cx, |pane, _| {
5614            assert!(!pane.can_navigate_backward());
5615            assert!(pane.can_navigate_forward());
5616        });
5617    }
5618
5619    #[gpui::test]
5620    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5621        init_test(cx);
5622        let fs = FakeFs::new(cx.executor());
5623
5624        let project = Project::test(fs, [], cx).await;
5625        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5626
5627        let panel = workspace.update(cx, |workspace, cx| {
5628            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5629            workspace.add_panel(panel.clone(), cx);
5630
5631            workspace
5632                .right_dock()
5633                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5634
5635            panel
5636        });
5637
5638        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5639        pane.update(cx, |pane, cx| {
5640            let item = cx.new_view(|cx| TestItem::new(cx));
5641            pane.add_item(Box::new(item), true, true, None, cx);
5642        });
5643
5644        // Transfer focus from center to panel
5645        workspace.update(cx, |workspace, cx| {
5646            workspace.toggle_panel_focus::<TestPanel>(cx);
5647        });
5648
5649        workspace.update(cx, |workspace, cx| {
5650            assert!(workspace.right_dock().read(cx).is_open());
5651            assert!(!panel.is_zoomed(cx));
5652            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5653        });
5654
5655        // Transfer focus from panel to center
5656        workspace.update(cx, |workspace, cx| {
5657            workspace.toggle_panel_focus::<TestPanel>(cx);
5658        });
5659
5660        workspace.update(cx, |workspace, cx| {
5661            assert!(workspace.right_dock().read(cx).is_open());
5662            assert!(!panel.is_zoomed(cx));
5663            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5664        });
5665
5666        // Close the dock
5667        workspace.update(cx, |workspace, cx| {
5668            workspace.toggle_dock(DockPosition::Right, cx);
5669        });
5670
5671        workspace.update(cx, |workspace, cx| {
5672            assert!(!workspace.right_dock().read(cx).is_open());
5673            assert!(!panel.is_zoomed(cx));
5674            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5675        });
5676
5677        // Open the dock
5678        workspace.update(cx, |workspace, cx| {
5679            workspace.toggle_dock(DockPosition::Right, cx);
5680        });
5681
5682        workspace.update(cx, |workspace, cx| {
5683            assert!(workspace.right_dock().read(cx).is_open());
5684            assert!(!panel.is_zoomed(cx));
5685            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5686        });
5687
5688        // Focus and zoom panel
5689        panel.update(cx, |panel, cx| {
5690            cx.focus_self();
5691            panel.set_zoomed(true, cx)
5692        });
5693
5694        workspace.update(cx, |workspace, cx| {
5695            assert!(workspace.right_dock().read(cx).is_open());
5696            assert!(panel.is_zoomed(cx));
5697            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5698        });
5699
5700        // Transfer focus to the center closes the dock
5701        workspace.update(cx, |workspace, cx| {
5702            workspace.toggle_panel_focus::<TestPanel>(cx);
5703        });
5704
5705        workspace.update(cx, |workspace, cx| {
5706            assert!(!workspace.right_dock().read(cx).is_open());
5707            assert!(panel.is_zoomed(cx));
5708            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5709        });
5710
5711        // Transferring focus back to the panel keeps it zoomed
5712        workspace.update(cx, |workspace, cx| {
5713            workspace.toggle_panel_focus::<TestPanel>(cx);
5714        });
5715
5716        workspace.update(cx, |workspace, cx| {
5717            assert!(workspace.right_dock().read(cx).is_open());
5718            assert!(panel.is_zoomed(cx));
5719            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5720        });
5721
5722        // Close the dock while it is zoomed
5723        workspace.update(cx, |workspace, cx| {
5724            workspace.toggle_dock(DockPosition::Right, cx)
5725        });
5726
5727        workspace.update(cx, |workspace, cx| {
5728            assert!(!workspace.right_dock().read(cx).is_open());
5729            assert!(panel.is_zoomed(cx));
5730            assert!(workspace.zoomed.is_none());
5731            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5732        });
5733
5734        // Opening the dock, when it's zoomed, retains focus
5735        workspace.update(cx, |workspace, cx| {
5736            workspace.toggle_dock(DockPosition::Right, cx)
5737        });
5738
5739        workspace.update(cx, |workspace, cx| {
5740            assert!(workspace.right_dock().read(cx).is_open());
5741            assert!(panel.is_zoomed(cx));
5742            assert!(workspace.zoomed.is_some());
5743            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5744        });
5745
5746        // Unzoom and close the panel, zoom the active pane.
5747        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5748        workspace.update(cx, |workspace, cx| {
5749            workspace.toggle_dock(DockPosition::Right, cx)
5750        });
5751        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5752
5753        // Opening a dock unzooms the pane.
5754        workspace.update(cx, |workspace, cx| {
5755            workspace.toggle_dock(DockPosition::Right, cx)
5756        });
5757        workspace.update(cx, |workspace, cx| {
5758            let pane = pane.read(cx);
5759            assert!(!pane.is_zoomed());
5760            assert!(!pane.focus_handle(cx).is_focused(cx));
5761            assert!(workspace.right_dock().read(cx).is_open());
5762            assert!(workspace.zoomed.is_none());
5763        });
5764    }
5765
5766    struct TestModal(FocusHandle);
5767
5768    impl TestModal {
5769        fn new(cx: &mut ViewContext<Self>) -> Self {
5770            Self(cx.focus_handle())
5771        }
5772    }
5773
5774    impl EventEmitter<DismissEvent> for TestModal {}
5775
5776    impl FocusableView for TestModal {
5777        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5778            self.0.clone()
5779        }
5780    }
5781
5782    impl ModalView for TestModal {}
5783
5784    impl Render for TestModal {
5785        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5786            div().track_focus(&self.0)
5787        }
5788    }
5789
5790    #[gpui::test]
5791    async fn test_panels(cx: &mut gpui::TestAppContext) {
5792        init_test(cx);
5793        let fs = FakeFs::new(cx.executor());
5794
5795        let project = Project::test(fs, [], cx).await;
5796        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5797
5798        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5799            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5800            workspace.add_panel(panel_1.clone(), cx);
5801            workspace
5802                .left_dock()
5803                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5804            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5805            workspace.add_panel(panel_2.clone(), cx);
5806            workspace
5807                .right_dock()
5808                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5809
5810            let left_dock = workspace.left_dock();
5811            assert_eq!(
5812                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5813                panel_1.panel_id()
5814            );
5815            assert_eq!(
5816                left_dock.read(cx).active_panel_size(cx).unwrap(),
5817                panel_1.size(cx)
5818            );
5819
5820            left_dock.update(cx, |left_dock, cx| {
5821                left_dock.resize_active_panel(Some(px(1337.)), cx)
5822            });
5823            assert_eq!(
5824                workspace
5825                    .right_dock()
5826                    .read(cx)
5827                    .visible_panel()
5828                    .unwrap()
5829                    .panel_id(),
5830                panel_2.panel_id(),
5831            );
5832
5833            (panel_1, panel_2)
5834        });
5835
5836        // Move panel_1 to the right
5837        panel_1.update(cx, |panel_1, cx| {
5838            panel_1.set_position(DockPosition::Right, cx)
5839        });
5840
5841        workspace.update(cx, |workspace, cx| {
5842            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5843            // Since it was the only panel on the left, the left dock should now be closed.
5844            assert!(!workspace.left_dock().read(cx).is_open());
5845            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5846            let right_dock = workspace.right_dock();
5847            assert_eq!(
5848                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5849                panel_1.panel_id()
5850            );
5851            assert_eq!(
5852                right_dock.read(cx).active_panel_size(cx).unwrap(),
5853                px(1337.)
5854            );
5855
5856            // Now we move panel_2 to the left
5857            panel_2.set_position(DockPosition::Left, cx);
5858        });
5859
5860        workspace.update(cx, |workspace, cx| {
5861            // Since panel_2 was not visible on the right, we don't open the left dock.
5862            assert!(!workspace.left_dock().read(cx).is_open());
5863            // And the right dock is unaffected in its displaying of panel_1
5864            assert!(workspace.right_dock().read(cx).is_open());
5865            assert_eq!(
5866                workspace
5867                    .right_dock()
5868                    .read(cx)
5869                    .visible_panel()
5870                    .unwrap()
5871                    .panel_id(),
5872                panel_1.panel_id(),
5873            );
5874        });
5875
5876        // Move panel_1 back to the left
5877        panel_1.update(cx, |panel_1, cx| {
5878            panel_1.set_position(DockPosition::Left, cx)
5879        });
5880
5881        workspace.update(cx, |workspace, cx| {
5882            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5883            let left_dock = workspace.left_dock();
5884            assert!(left_dock.read(cx).is_open());
5885            assert_eq!(
5886                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5887                panel_1.panel_id()
5888            );
5889            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5890            // And the right dock should be closed as it no longer has any panels.
5891            assert!(!workspace.right_dock().read(cx).is_open());
5892
5893            // Now we move panel_1 to the bottom
5894            panel_1.set_position(DockPosition::Bottom, cx);
5895        });
5896
5897        workspace.update(cx, |workspace, cx| {
5898            // Since panel_1 was visible on the left, we close the left dock.
5899            assert!(!workspace.left_dock().read(cx).is_open());
5900            // The bottom dock is sized based on the panel's default size,
5901            // since the panel orientation changed from vertical to horizontal.
5902            let bottom_dock = workspace.bottom_dock();
5903            assert_eq!(
5904                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5905                panel_1.size(cx),
5906            );
5907            // Close bottom dock and move panel_1 back to the left.
5908            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5909            panel_1.set_position(DockPosition::Left, cx);
5910        });
5911
5912        // Emit activated event on panel 1
5913        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5914
5915        // Now the left dock is open and panel_1 is active and focused.
5916        workspace.update(cx, |workspace, cx| {
5917            let left_dock = workspace.left_dock();
5918            assert!(left_dock.read(cx).is_open());
5919            assert_eq!(
5920                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5921                panel_1.panel_id(),
5922            );
5923            assert!(panel_1.focus_handle(cx).is_focused(cx));
5924        });
5925
5926        // Emit closed event on panel 2, which is not active
5927        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5928
5929        // Wo don't close the left dock, because panel_2 wasn't the active panel
5930        workspace.update(cx, |workspace, cx| {
5931            let left_dock = workspace.left_dock();
5932            assert!(left_dock.read(cx).is_open());
5933            assert_eq!(
5934                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5935                panel_1.panel_id(),
5936            );
5937        });
5938
5939        // Emitting a ZoomIn event shows the panel as zoomed.
5940        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5941        workspace.update(cx, |workspace, _| {
5942            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5943            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5944        });
5945
5946        // Move panel to another dock while it is zoomed
5947        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5948        workspace.update(cx, |workspace, _| {
5949            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5950
5951            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5952        });
5953
5954        // This is a helper for getting a:
5955        // - valid focus on an element,
5956        // - that isn't a part of the panes and panels system of the Workspace,
5957        // - and doesn't trigger the 'on_focus_lost' API.
5958        let focus_other_view = {
5959            let workspace = workspace.clone();
5960            move |cx: &mut VisualTestContext| {
5961                workspace.update(cx, |workspace, cx| {
5962                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5963                        workspace.toggle_modal(cx, TestModal::new);
5964                        workspace.toggle_modal(cx, TestModal::new);
5965                    } else {
5966                        workspace.toggle_modal(cx, TestModal::new);
5967                    }
5968                })
5969            }
5970        };
5971
5972        // If focus is transferred to another view that's not a panel or another pane, we still show
5973        // the panel as zoomed.
5974        focus_other_view(cx);
5975        workspace.update(cx, |workspace, _| {
5976            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5977            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5978        });
5979
5980        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5981        workspace.update(cx, |_, cx| cx.focus_self());
5982        workspace.update(cx, |workspace, _| {
5983            assert_eq!(workspace.zoomed, None);
5984            assert_eq!(workspace.zoomed_position, None);
5985        });
5986
5987        // If focus is transferred again to another view that's not a panel or a pane, we won't
5988        // show the panel as zoomed because it wasn't zoomed before.
5989        focus_other_view(cx);
5990        workspace.update(cx, |workspace, _| {
5991            assert_eq!(workspace.zoomed, None);
5992            assert_eq!(workspace.zoomed_position, None);
5993        });
5994
5995        // When the panel is activated, it is zoomed again.
5996        cx.dispatch_action(ToggleRightDock);
5997        workspace.update(cx, |workspace, _| {
5998            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5999            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6000        });
6001
6002        // Emitting a ZoomOut event unzooms the panel.
6003        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
6004        workspace.update(cx, |workspace, _| {
6005            assert_eq!(workspace.zoomed, None);
6006            assert_eq!(workspace.zoomed_position, None);
6007        });
6008
6009        // Emit closed event on panel 1, which is active
6010        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6011
6012        // Now the left dock is closed, because panel_1 was the active panel
6013        workspace.update(cx, |workspace, cx| {
6014            let right_dock = workspace.right_dock();
6015            assert!(!right_dock.read(cx).is_open());
6016        });
6017    }
6018
6019    mod register_project_item_tests {
6020        use ui::Context as _;
6021
6022        use super::*;
6023
6024        const TEST_PNG_KIND: &str = "TestPngItemView";
6025        // View
6026        struct TestPngItemView {
6027            focus_handle: FocusHandle,
6028        }
6029        // Model
6030        struct TestPngItem {}
6031
6032        impl project::Item for TestPngItem {
6033            fn try_open(
6034                _project: &Model<Project>,
6035                path: &ProjectPath,
6036                cx: &mut AppContext,
6037            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6038                if path.path.extension().unwrap() == "png" {
6039                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
6040                } else {
6041                    None
6042                }
6043            }
6044
6045            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6046                None
6047            }
6048
6049            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6050                None
6051            }
6052        }
6053
6054        impl Item for TestPngItemView {
6055            type Event = ();
6056
6057            fn serialized_item_kind() -> Option<&'static str> {
6058                Some(TEST_PNG_KIND)
6059            }
6060        }
6061        impl EventEmitter<()> for TestPngItemView {}
6062        impl FocusableView for TestPngItemView {
6063            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6064                self.focus_handle.clone()
6065            }
6066        }
6067
6068        impl Render for TestPngItemView {
6069            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6070                Empty
6071            }
6072        }
6073
6074        impl ProjectItem for TestPngItemView {
6075            type Item = TestPngItem;
6076
6077            fn for_project_item(
6078                _project: Model<Project>,
6079                _item: Model<Self::Item>,
6080                cx: &mut ViewContext<Self>,
6081            ) -> Self
6082            where
6083                Self: Sized,
6084            {
6085                Self {
6086                    focus_handle: cx.focus_handle(),
6087                }
6088            }
6089        }
6090
6091        const TEST_IPYNB_KIND: &str = "TestIpynbItemView";
6092        // View
6093        struct TestIpynbItemView {
6094            focus_handle: FocusHandle,
6095        }
6096        // Model
6097        struct TestIpynbItem {}
6098
6099        impl project::Item for TestIpynbItem {
6100            fn try_open(
6101                _project: &Model<Project>,
6102                path: &ProjectPath,
6103                cx: &mut AppContext,
6104            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6105                if path.path.extension().unwrap() == "ipynb" {
6106                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
6107                } else {
6108                    None
6109                }
6110            }
6111
6112            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6113                None
6114            }
6115
6116            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6117                None
6118            }
6119        }
6120
6121        impl Item for TestIpynbItemView {
6122            type Event = ();
6123
6124            fn serialized_item_kind() -> Option<&'static str> {
6125                Some(TEST_IPYNB_KIND)
6126            }
6127        }
6128        impl EventEmitter<()> for TestIpynbItemView {}
6129        impl FocusableView for TestIpynbItemView {
6130            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6131                self.focus_handle.clone()
6132            }
6133        }
6134
6135        impl Render for TestIpynbItemView {
6136            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6137                Empty
6138            }
6139        }
6140
6141        impl ProjectItem for TestIpynbItemView {
6142            type Item = TestIpynbItem;
6143
6144            fn for_project_item(
6145                _project: Model<Project>,
6146                _item: Model<Self::Item>,
6147                cx: &mut ViewContext<Self>,
6148            ) -> Self
6149            where
6150                Self: Sized,
6151            {
6152                Self {
6153                    focus_handle: cx.focus_handle(),
6154                }
6155            }
6156        }
6157
6158        struct TestAlternatePngItemView {
6159            focus_handle: FocusHandle,
6160        }
6161
6162        const TEST_ALTERNATE_PNG_KIND: &str = "TestAlternatePngItemView";
6163        impl Item for TestAlternatePngItemView {
6164            type Event = ();
6165
6166            fn serialized_item_kind() -> Option<&'static str> {
6167                Some(TEST_ALTERNATE_PNG_KIND)
6168            }
6169        }
6170        impl EventEmitter<()> for TestAlternatePngItemView {}
6171        impl FocusableView for TestAlternatePngItemView {
6172            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6173                self.focus_handle.clone()
6174            }
6175        }
6176
6177        impl Render for TestAlternatePngItemView {
6178            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6179                Empty
6180            }
6181        }
6182
6183        impl ProjectItem for TestAlternatePngItemView {
6184            type Item = TestPngItem;
6185
6186            fn for_project_item(
6187                _project: Model<Project>,
6188                _item: Model<Self::Item>,
6189                cx: &mut ViewContext<Self>,
6190            ) -> Self
6191            where
6192                Self: Sized,
6193            {
6194                Self {
6195                    focus_handle: cx.focus_handle(),
6196                }
6197            }
6198        }
6199
6200        #[gpui::test]
6201        async fn test_register_project_item(cx: &mut TestAppContext) {
6202            init_test(cx);
6203
6204            cx.update(|cx| {
6205                register_project_item::<TestPngItemView>(cx);
6206                register_project_item::<TestIpynbItemView>(cx);
6207            });
6208
6209            let fs = FakeFs::new(cx.executor());
6210            fs.insert_tree(
6211                "/root1",
6212                json!({
6213                    "one.png": "BINARYDATAHERE",
6214                    "two.ipynb": "{ totally a notebook }",
6215                    "three.txt": "editing text, sure why not?"
6216                }),
6217            )
6218            .await;
6219
6220            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6221            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6222
6223            let worktree_id = project.update(cx, |project, cx| {
6224                project.worktrees().next().unwrap().read(cx).id()
6225            });
6226
6227            let handle = workspace
6228                .update(cx, |workspace, cx| {
6229                    let project_path = (worktree_id, "one.png");
6230                    workspace.open_path(project_path, None, true, cx)
6231                })
6232                .await
6233                .unwrap();
6234
6235            // Now we can check if the handle we got back errored or not
6236            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_PNG_KIND);
6237
6238            let handle = workspace
6239                .update(cx, |workspace, cx| {
6240                    let project_path = (worktree_id, "two.ipynb");
6241                    workspace.open_path(project_path, None, true, cx)
6242                })
6243                .await
6244                .unwrap();
6245
6246            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_IPYNB_KIND);
6247
6248            let handle = workspace
6249                .update(cx, |workspace, cx| {
6250                    let project_path = (worktree_id, "three.txt");
6251                    workspace.open_path(project_path, None, true, cx)
6252                })
6253                .await;
6254            assert!(handle.is_err());
6255        }
6256
6257        #[gpui::test]
6258        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
6259            init_test(cx);
6260
6261            cx.update(|cx| {
6262                register_project_item::<TestPngItemView>(cx);
6263                register_project_item::<TestAlternatePngItemView>(cx);
6264            });
6265
6266            let fs = FakeFs::new(cx.executor());
6267            fs.insert_tree(
6268                "/root1",
6269                json!({
6270                    "one.png": "BINARYDATAHERE",
6271                    "two.ipynb": "{ totally a notebook }",
6272                    "three.txt": "editing text, sure why not?"
6273                }),
6274            )
6275            .await;
6276
6277            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6278            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6279
6280            let worktree_id = project.update(cx, |project, cx| {
6281                project.worktrees().next().unwrap().read(cx).id()
6282            });
6283
6284            let handle = workspace
6285                .update(cx, |workspace, cx| {
6286                    let project_path = (worktree_id, "one.png");
6287                    workspace.open_path(project_path, None, true, cx)
6288                })
6289                .await
6290                .unwrap();
6291
6292            // This _must_ be the second item registered
6293            assert_eq!(
6294                handle.serialized_item_kind().unwrap(),
6295                TEST_ALTERNATE_PNG_KIND
6296            );
6297
6298            let handle = workspace
6299                .update(cx, |workspace, cx| {
6300                    let project_path = (worktree_id, "three.txt");
6301                    workspace.open_path(project_path, None, true, cx)
6302                })
6303                .await;
6304            assert!(handle.is_err());
6305        }
6306    }
6307
6308    pub fn init_test(cx: &mut TestAppContext) {
6309        cx.update(|cx| {
6310            let settings_store = SettingsStore::test(cx);
6311            cx.set_global(settings_store);
6312            theme::init(theme::LoadThemes::JustBase, cx);
6313            language::init(cx);
6314            crate::init_settings(cx);
6315            Project::init_settings(cx);
6316        });
6317    }
6318}