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