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        let centered_layout = self.centered_layout
4734            && self.center.panes().len() == 1
4735            && self.active_item(cx).is_some();
4736        let render_padding = |size| {
4737            (size > 0.0).then(|| {
4738                div()
4739                    .h_full()
4740                    .w(relative(size))
4741                    .bg(cx.theme().colors().editor_background)
4742                    .border_color(cx.theme().colors().pane_group_border)
4743            })
4744        };
4745        let paddings = if centered_layout {
4746            let settings = WorkspaceSettings::get_global(cx).centered_layout;
4747            (
4748                render_padding(Self::adjust_padding(settings.left_padding)),
4749                render_padding(Self::adjust_padding(settings.right_padding)),
4750            )
4751        } else {
4752            (None, None)
4753        };
4754        let ui_font = theme::setup_ui_font(cx);
4755
4756        let theme = cx.theme().clone();
4757        let colors = theme.colors();
4758
4759        client_side_decorations(
4760            self.actions(div(), cx)
4761                .key_context(context)
4762                .relative()
4763                .size_full()
4764                .flex()
4765                .flex_col()
4766                .font(ui_font)
4767                .gap_0()
4768                .justify_start()
4769                .items_start()
4770                .text_color(colors.text)
4771                .overflow_hidden()
4772                .children(self.titlebar_item.clone())
4773                .child(
4774                    div()
4775                        .size_full()
4776                        .relative()
4777                        .flex_1()
4778                        .flex()
4779                        .flex_col()
4780                        .child(
4781                            div()
4782                                .id("workspace")
4783                                .bg(colors.background)
4784                                .relative()
4785                                .flex_1()
4786                                .w_full()
4787                                .flex()
4788                                .flex_col()
4789                                .overflow_hidden()
4790                                .border_t_1()
4791                                .border_b_1()
4792                                .border_color(colors.border)
4793                                .child({
4794                                    let this = cx.view().clone();
4795                                    canvas(
4796                                        move |bounds, cx| {
4797                                            this.update(cx, |this, _cx| this.bounds = bounds)
4798                                        },
4799                                        |_, _, _| {},
4800                                    )
4801                                    .absolute()
4802                                    .size_full()
4803                                })
4804                                .when(self.zoomed.is_none(), |this| {
4805                                    this.on_drag_move(cx.listener(
4806                                        |workspace, e: &DragMoveEvent<DraggedDock>, cx| {
4807                                            match e.drag(cx).0 {
4808                                                DockPosition::Left => {
4809                                                    let size = e.event.position.x
4810                                                        - workspace.bounds.left();
4811                                                    workspace.left_dock.update(
4812                                                        cx,
4813                                                        |left_dock, cx| {
4814                                                            left_dock.resize_active_panel(
4815                                                                Some(size),
4816                                                                cx,
4817                                                            );
4818                                                        },
4819                                                    );
4820                                                }
4821                                                DockPosition::Right => {
4822                                                    let size = workspace.bounds.right()
4823                                                        - e.event.position.x;
4824                                                    workspace.right_dock.update(
4825                                                        cx,
4826                                                        |right_dock, cx| {
4827                                                            right_dock.resize_active_panel(
4828                                                                Some(size),
4829                                                                cx,
4830                                                            );
4831                                                        },
4832                                                    );
4833                                                }
4834                                                DockPosition::Bottom => {
4835                                                    let size = workspace.bounds.bottom()
4836                                                        - e.event.position.y;
4837                                                    workspace.bottom_dock.update(
4838                                                        cx,
4839                                                        |bottom_dock, cx| {
4840                                                            bottom_dock.resize_active_panel(
4841                                                                Some(size),
4842                                                                cx,
4843                                                            );
4844                                                        },
4845                                                    );
4846                                                }
4847                                            }
4848                                        },
4849                                    ))
4850                                })
4851                                .child(
4852                                    div()
4853                                        .flex()
4854                                        .flex_row()
4855                                        .h_full()
4856                                        // Left Dock
4857                                        .children(self.render_dock(
4858                                            DockPosition::Left,
4859                                            &self.left_dock,
4860                                            cx,
4861                                        ))
4862                                        // Panes
4863                                        .child(
4864                                            div()
4865                                                .flex()
4866                                                .flex_col()
4867                                                .flex_1()
4868                                                .overflow_hidden()
4869                                                .child(
4870                                                    h_flex()
4871                                                        .flex_1()
4872                                                        .when_some(paddings.0, |this, p| {
4873                                                            this.child(p.border_r_1())
4874                                                        })
4875                                                        .child(self.center.render(
4876                                                            &self.project,
4877                                                            &self.follower_states,
4878                                                            self.active_call(),
4879                                                            &self.active_pane,
4880                                                            self.zoomed.as_ref(),
4881                                                            &self.app_state,
4882                                                            cx,
4883                                                        ))
4884                                                        .when_some(paddings.1, |this, p| {
4885                                                            this.child(p.border_l_1())
4886                                                        }),
4887                                                )
4888                                                .children(self.render_dock(
4889                                                    DockPosition::Bottom,
4890                                                    &self.bottom_dock,
4891                                                    cx,
4892                                                )),
4893                                        )
4894                                        // Right Dock
4895                                        .children(self.render_dock(
4896                                            DockPosition::Right,
4897                                            &self.right_dock,
4898                                            cx,
4899                                        )),
4900                                )
4901                                .children(self.zoomed.as_ref().and_then(|view| {
4902                                    let zoomed_view = view.upgrade()?;
4903                                    let div = div()
4904                                        .occlude()
4905                                        .absolute()
4906                                        .overflow_hidden()
4907                                        .border_color(colors.border)
4908                                        .bg(colors.background)
4909                                        .child(zoomed_view)
4910                                        .inset_0()
4911                                        .shadow_lg();
4912
4913                                    Some(match self.zoomed_position {
4914                                        Some(DockPosition::Left) => div.right_2().border_r_1(),
4915                                        Some(DockPosition::Right) => div.left_2().border_l_1(),
4916                                        Some(DockPosition::Bottom) => div.top_2().border_t_1(),
4917                                        None => {
4918                                            div.top_2().bottom_2().left_2().right_2().border_1()
4919                                        }
4920                                    })
4921                                }))
4922                                .children(self.render_notifications(cx)),
4923                        )
4924                        .child(self.status_bar.clone())
4925                        .child(self.modal_layer.clone()),
4926                ),
4927            cx,
4928        )
4929    }
4930}
4931
4932impl WorkspaceStore {
4933    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4934        Self {
4935            workspaces: Default::default(),
4936            _subscriptions: vec![
4937                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4938                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4939            ],
4940            client,
4941        }
4942    }
4943
4944    pub fn update_followers(
4945        &self,
4946        project_id: Option<u64>,
4947        update: proto::update_followers::Variant,
4948        cx: &AppContext,
4949    ) -> Option<()> {
4950        let active_call = ActiveCall::try_global(cx)?;
4951        let room_id = active_call.read(cx).room()?.read(cx).id();
4952        self.client
4953            .send(proto::UpdateFollowers {
4954                room_id,
4955                project_id,
4956                variant: Some(update),
4957            })
4958            .log_err()
4959    }
4960
4961    pub async fn handle_follow(
4962        this: Model<Self>,
4963        envelope: TypedEnvelope<proto::Follow>,
4964        mut cx: AsyncAppContext,
4965    ) -> Result<proto::FollowResponse> {
4966        this.update(&mut cx, |this, cx| {
4967            let follower = Follower {
4968                project_id: envelope.payload.project_id,
4969                peer_id: envelope.original_sender_id()?,
4970            };
4971
4972            let mut response = proto::FollowResponse::default();
4973            this.workspaces.retain(|workspace| {
4974                workspace
4975                    .update(cx, |workspace, cx| {
4976                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4977                        if let Some(active_view) = handler_response.active_view.clone() {
4978                            if workspace.project.read(cx).remote_id() == follower.project_id {
4979                                response.active_view = Some(active_view)
4980                            }
4981                        }
4982                    })
4983                    .is_ok()
4984            });
4985
4986            Ok(response)
4987        })?
4988    }
4989
4990    async fn handle_update_followers(
4991        this: Model<Self>,
4992        envelope: TypedEnvelope<proto::UpdateFollowers>,
4993        mut cx: AsyncAppContext,
4994    ) -> Result<()> {
4995        let leader_id = envelope.original_sender_id()?;
4996        let update = envelope.payload;
4997
4998        this.update(&mut cx, |this, cx| {
4999            this.workspaces.retain(|workspace| {
5000                workspace
5001                    .update(cx, |workspace, cx| {
5002                        let project_id = workspace.project.read(cx).remote_id();
5003                        if update.project_id != project_id && update.project_id.is_some() {
5004                            return;
5005                        }
5006                        workspace.handle_update_followers(leader_id, update.clone(), cx);
5007                    })
5008                    .is_ok()
5009            });
5010            Ok(())
5011        })?
5012    }
5013}
5014
5015impl ViewId {
5016    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
5017        Ok(Self {
5018            creator: message
5019                .creator
5020                .ok_or_else(|| anyhow!("creator is missing"))?,
5021            id: message.id,
5022        })
5023    }
5024
5025    pub(crate) fn to_proto(self) -> proto::ViewId {
5026        proto::ViewId {
5027            creator: Some(self.creator),
5028            id: self.id,
5029        }
5030    }
5031}
5032
5033impl FollowerState {
5034    fn pane(&self) -> &View<Pane> {
5035        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
5036    }
5037}
5038
5039pub trait WorkspaceHandle {
5040    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
5041}
5042
5043impl WorkspaceHandle for View<Workspace> {
5044    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
5045        self.read(cx)
5046            .worktrees(cx)
5047            .flat_map(|worktree| {
5048                let worktree_id = worktree.read(cx).id();
5049                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
5050                    worktree_id,
5051                    path: f.path.clone(),
5052                })
5053            })
5054            .collect::<Vec<_>>()
5055    }
5056}
5057
5058impl std::fmt::Debug for OpenPaths {
5059    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5060        f.debug_struct("OpenPaths")
5061            .field("paths", &self.paths)
5062            .finish()
5063    }
5064}
5065
5066pub fn activate_workspace_for_project(
5067    cx: &mut AppContext,
5068    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
5069) -> Option<WindowHandle<Workspace>> {
5070    for window in cx.windows() {
5071        let Some(workspace) = window.downcast::<Workspace>() else {
5072            continue;
5073        };
5074
5075        let predicate = workspace
5076            .update(cx, |workspace, cx| {
5077                let project = workspace.project.read(cx);
5078                if predicate(project, cx) {
5079                    cx.activate_window();
5080                    true
5081                } else {
5082                    false
5083                }
5084            })
5085            .log_err()
5086            .unwrap_or(false);
5087
5088        if predicate {
5089            return Some(workspace);
5090        }
5091    }
5092
5093    None
5094}
5095
5096pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
5097    DB.last_workspace().await.log_err().flatten()
5098}
5099
5100pub fn last_session_workspace_locations(
5101    last_session_id: &str,
5102    last_session_window_stack: Option<Vec<WindowId>>,
5103) -> Option<Vec<SerializedWorkspaceLocation>> {
5104    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
5105        .log_err()
5106}
5107
5108actions!(collab, [OpenChannelNotes]);
5109actions!(zed, [OpenLog]);
5110
5111async fn join_channel_internal(
5112    channel_id: ChannelId,
5113    app_state: &Arc<AppState>,
5114    requesting_window: Option<WindowHandle<Workspace>>,
5115    active_call: &Model<ActiveCall>,
5116    cx: &mut AsyncAppContext,
5117) -> Result<bool> {
5118    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
5119        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
5120            return (false, None);
5121        };
5122
5123        let already_in_channel = room.channel_id() == Some(channel_id);
5124        let should_prompt = room.is_sharing_project()
5125            && !room.remote_participants().is_empty()
5126            && !already_in_channel;
5127        let open_room = if already_in_channel {
5128            active_call.room().cloned()
5129        } else {
5130            None
5131        };
5132        (should_prompt, open_room)
5133    })?;
5134
5135    if let Some(room) = open_room {
5136        let task = room.update(cx, |room, cx| {
5137            if let Some((project, host)) = room.most_active_project(cx) {
5138                return Some(join_in_room_project(project, host, app_state.clone(), cx));
5139            }
5140
5141            None
5142        })?;
5143        if let Some(task) = task {
5144            task.await?;
5145        }
5146        return anyhow::Ok(true);
5147    }
5148
5149    if should_prompt {
5150        if let Some(workspace) = requesting_window {
5151            let answer = workspace
5152                .update(cx, |_, cx| {
5153                    cx.prompt(
5154                        PromptLevel::Warning,
5155                        "Do you want to switch channels?",
5156                        Some("Leaving this call will unshare your current project."),
5157                        &["Yes, Join Channel", "Cancel"],
5158                    )
5159                })?
5160                .await;
5161
5162            if answer == Ok(1) {
5163                return Ok(false);
5164            }
5165        } else {
5166            return Ok(false); // unreachable!() hopefully
5167        }
5168    }
5169
5170    let client = cx.update(|cx| active_call.read(cx).client())?;
5171
5172    let mut client_status = client.status();
5173
5174    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
5175    'outer: loop {
5176        let Some(status) = client_status.recv().await else {
5177            return Err(anyhow!("error connecting"));
5178        };
5179
5180        match status {
5181            Status::Connecting
5182            | Status::Authenticating
5183            | Status::Reconnecting
5184            | Status::Reauthenticating => continue,
5185            Status::Connected { .. } => break 'outer,
5186            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
5187            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
5188            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
5189                return Err(ErrorCode::Disconnected.into());
5190            }
5191        }
5192    }
5193
5194    let room = active_call
5195        .update(cx, |active_call, cx| {
5196            active_call.join_channel(channel_id, cx)
5197        })?
5198        .await?;
5199
5200    let Some(room) = room else {
5201        return anyhow::Ok(true);
5202    };
5203
5204    room.update(cx, |room, _| room.room_update_completed())?
5205        .await;
5206
5207    let task = room.update(cx, |room, cx| {
5208        if let Some((project, host)) = room.most_active_project(cx) {
5209            return Some(join_in_room_project(project, host, app_state.clone(), cx));
5210        }
5211
5212        // If you are the first to join a channel, see if you should share your project.
5213        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
5214            if let Some(workspace) = requesting_window {
5215                let project = workspace.update(cx, |workspace, cx| {
5216                    let project = workspace.project.read(cx);
5217
5218                    if !CallSettings::get_global(cx).share_on_join {
5219                        return None;
5220                    }
5221
5222                    if (project.is_local() || project.is_via_ssh())
5223                        && project.visible_worktrees(cx).any(|tree| {
5224                            tree.read(cx)
5225                                .root_entry()
5226                                .map_or(false, |entry| entry.is_dir())
5227                        })
5228                    {
5229                        Some(workspace.project.clone())
5230                    } else {
5231                        None
5232                    }
5233                });
5234                if let Ok(Some(project)) = project {
5235                    return Some(cx.spawn(|room, mut cx| async move {
5236                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
5237                            .await?;
5238                        Ok(())
5239                    }));
5240                }
5241            }
5242        }
5243
5244        None
5245    })?;
5246    if let Some(task) = task {
5247        task.await?;
5248        return anyhow::Ok(true);
5249    }
5250    anyhow::Ok(false)
5251}
5252
5253pub fn join_channel(
5254    channel_id: ChannelId,
5255    app_state: Arc<AppState>,
5256    requesting_window: Option<WindowHandle<Workspace>>,
5257    cx: &mut AppContext,
5258) -> Task<Result<()>> {
5259    let active_call = ActiveCall::global(cx);
5260    cx.spawn(|mut cx| async move {
5261        let result = join_channel_internal(
5262            channel_id,
5263            &app_state,
5264            requesting_window,
5265            &active_call,
5266            &mut cx,
5267        )
5268            .await;
5269
5270        // join channel succeeded, and opened a window
5271        if matches!(result, Ok(true)) {
5272            return anyhow::Ok(());
5273        }
5274
5275        // find an existing workspace to focus and show call controls
5276        let mut active_window =
5277            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
5278        if active_window.is_none() {
5279            // no open workspaces, make one to show the error in (blergh)
5280            let (window_handle, _) = cx
5281                .update(|cx| {
5282                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
5283                })?
5284                .await?;
5285
5286            if result.is_ok() {
5287                cx.update(|cx| {
5288                    cx.dispatch_action(&OpenChannelNotes);
5289                }).log_err();
5290            }
5291
5292            active_window = Some(window_handle);
5293        }
5294
5295        if let Err(err) = result {
5296            log::error!("failed to join channel: {}", err);
5297            if let Some(active_window) = active_window {
5298                active_window
5299                    .update(&mut cx, |_, cx| {
5300                        let detail: SharedString = match err.error_code() {
5301                            ErrorCode::SignedOut => {
5302                                "Please sign in to continue.".into()
5303                            }
5304                            ErrorCode::UpgradeRequired => {
5305                                "Your are running an unsupported version of Zed. Please update to continue.".into()
5306                            }
5307                            ErrorCode::NoSuchChannel => {
5308                                "No matching channel was found. Please check the link and try again.".into()
5309                            }
5310                            ErrorCode::Forbidden => {
5311                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
5312                            }
5313                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
5314                            _ => format!("{}\n\nPlease try again.", err).into(),
5315                        };
5316                        cx.prompt(
5317                            PromptLevel::Critical,
5318                            "Failed to join channel",
5319                            Some(&detail),
5320                            &["Ok"],
5321                        )
5322                    })?
5323                    .await
5324                    .ok();
5325            }
5326        }
5327
5328        // return ok, we showed the error to the user.
5329        anyhow::Ok(())
5330    })
5331}
5332
5333pub async fn get_any_active_workspace(
5334    app_state: Arc<AppState>,
5335    mut cx: AsyncAppContext,
5336) -> anyhow::Result<WindowHandle<Workspace>> {
5337    // find an existing workspace to focus and show call controls
5338    let active_window = activate_any_workspace_window(&mut cx);
5339    if active_window.is_none() {
5340        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
5341            .await?;
5342    }
5343    activate_any_workspace_window(&mut cx).context("could not open zed")
5344}
5345
5346fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
5347    cx.update(|cx| {
5348        if let Some(workspace_window) = cx
5349            .active_window()
5350            .and_then(|window| window.downcast::<Workspace>())
5351        {
5352            return Some(workspace_window);
5353        }
5354
5355        for window in cx.windows() {
5356            if let Some(workspace_window) = window.downcast::<Workspace>() {
5357                workspace_window
5358                    .update(cx, |_, cx| cx.activate_window())
5359                    .ok();
5360                return Some(workspace_window);
5361            }
5362        }
5363        None
5364    })
5365    .ok()
5366    .flatten()
5367}
5368
5369pub fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
5370    cx.windows()
5371        .into_iter()
5372        .filter_map(|window| window.downcast::<Workspace>())
5373        .filter(|workspace| {
5374            workspace
5375                .read(cx)
5376                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
5377        })
5378        .collect()
5379}
5380
5381#[derive(Default)]
5382pub struct OpenOptions {
5383    pub open_new_workspace: Option<bool>,
5384    pub replace_window: Option<WindowHandle<Workspace>>,
5385    pub env: Option<HashMap<String, String>>,
5386}
5387
5388#[allow(clippy::type_complexity)]
5389pub fn open_paths(
5390    abs_paths: &[PathBuf],
5391    app_state: Arc<AppState>,
5392    open_options: OpenOptions,
5393    cx: &mut AppContext,
5394) -> Task<
5395    anyhow::Result<(
5396        WindowHandle<Workspace>,
5397        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
5398    )>,
5399> {
5400    let abs_paths = abs_paths.to_vec();
5401    let mut existing = None;
5402    let mut best_match = None;
5403    let mut open_visible = OpenVisible::All;
5404
5405    if open_options.open_new_workspace != Some(true) {
5406        for window in local_workspace_windows(cx) {
5407            if let Ok(workspace) = window.read(cx) {
5408                let m = workspace
5409                    .project
5410                    .read(cx)
5411                    .visibility_for_paths(&abs_paths, cx);
5412                if m > best_match {
5413                    existing = Some(window);
5414                    best_match = m;
5415                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
5416                    existing = Some(window)
5417                }
5418            }
5419        }
5420    }
5421
5422    cx.spawn(move |mut cx| async move {
5423        if open_options.open_new_workspace.is_none() && existing.is_none() {
5424            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
5425            if futures::future::join_all(all_files)
5426                .await
5427                .into_iter()
5428                .filter_map(|result| result.ok().flatten())
5429                .all(|file| !file.is_dir)
5430            {
5431                cx.update(|cx| {
5432                    for window in local_workspace_windows(cx) {
5433                        if let Ok(workspace) = window.read(cx) {
5434                            let project = workspace.project().read(cx);
5435                            if project.is_via_collab() {
5436                                continue;
5437                            }
5438                            existing = Some(window);
5439                            open_visible = OpenVisible::None;
5440                            break;
5441                        }
5442                    }
5443                })?;
5444            }
5445        }
5446
5447        if let Some(existing) = existing {
5448            Ok((
5449                existing,
5450                existing
5451                    .update(&mut cx, |workspace, cx| {
5452                        cx.activate_window();
5453                        workspace.open_paths(abs_paths, open_visible, None, cx)
5454                    })?
5455                    .await,
5456            ))
5457        } else {
5458            cx.update(move |cx| {
5459                Workspace::new_local(
5460                    abs_paths,
5461                    app_state.clone(),
5462                    open_options.replace_window,
5463                    open_options.env,
5464                    cx,
5465                )
5466            })?
5467            .await
5468        }
5469    })
5470}
5471
5472pub fn open_new(
5473    open_options: OpenOptions,
5474    app_state: Arc<AppState>,
5475    cx: &mut AppContext,
5476    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
5477) -> Task<anyhow::Result<()>> {
5478    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
5479    cx.spawn(|mut cx| async move {
5480        let (workspace, opened_paths) = task.await?;
5481        workspace.update(&mut cx, |workspace, cx| {
5482            if opened_paths.is_empty() {
5483                init(workspace, cx)
5484            }
5485        })?;
5486        Ok(())
5487    })
5488}
5489
5490pub fn create_and_open_local_file(
5491    path: &'static Path,
5492    cx: &mut ViewContext<Workspace>,
5493    default_content: impl 'static + Send + FnOnce() -> Rope,
5494) -> Task<Result<Box<dyn ItemHandle>>> {
5495    cx.spawn(|workspace, mut cx| async move {
5496        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
5497        if !fs.is_file(path).await {
5498            fs.create_file(path, Default::default()).await?;
5499            fs.save(path, &default_content(), Default::default())
5500                .await?;
5501        }
5502
5503        let mut items = workspace
5504            .update(&mut cx, |workspace, cx| {
5505                workspace.with_local_workspace(cx, |workspace, cx| {
5506                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
5507                })
5508            })?
5509            .await?
5510            .await;
5511
5512        let item = items.pop().flatten();
5513        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
5514    })
5515}
5516
5517pub fn open_ssh_project(
5518    window: WindowHandle<Workspace>,
5519    connection_options: SshConnectionOptions,
5520    cancel_rx: oneshot::Receiver<()>,
5521    delegate: Arc<dyn SshClientDelegate>,
5522    app_state: Arc<AppState>,
5523    paths: Vec<PathBuf>,
5524    cx: &mut AppContext,
5525) -> Task<Result<()>> {
5526    cx.spawn(|mut cx| async move {
5527        let (serialized_ssh_project, workspace_id, serialized_workspace) =
5528            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
5529
5530        let session = match cx
5531            .update(|cx| {
5532                remote::SshRemoteClient::new(
5533                    ConnectionIdentifier::Workspace(workspace_id.0),
5534                    connection_options,
5535                    cancel_rx,
5536                    delegate,
5537                    cx,
5538                )
5539            })?
5540            .await?
5541        {
5542            Some(result) => result,
5543            None => return Ok(()),
5544        };
5545
5546        let project = cx.update(|cx| {
5547            project::Project::ssh(
5548                session,
5549                app_state.client.clone(),
5550                app_state.node_runtime.clone(),
5551                app_state.user_store.clone(),
5552                app_state.languages.clone(),
5553                app_state.fs.clone(),
5554                cx,
5555            )
5556        })?;
5557
5558        let toolchains = DB.toolchains(workspace_id).await?;
5559        for (toolchain, worktree_id) in toolchains {
5560            project
5561                .update(&mut cx, |this, cx| {
5562                    this.activate_toolchain(worktree_id, toolchain, cx)
5563                })?
5564                .await;
5565        }
5566        let mut project_paths_to_open = vec![];
5567        let mut project_path_errors = vec![];
5568
5569        for path in paths {
5570            let result = cx
5571                .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))?
5572                .await;
5573            match result {
5574                Ok((_, project_path)) => {
5575                    project_paths_to_open.push((path.clone(), Some(project_path)));
5576                }
5577                Err(error) => {
5578                    project_path_errors.push(error);
5579                }
5580            };
5581        }
5582
5583        if project_paths_to_open.is_empty() {
5584            return Err(project_path_errors
5585                .pop()
5586                .unwrap_or_else(|| anyhow!("no paths given")));
5587        }
5588
5589        cx.update_window(window.into(), |_, cx| {
5590            cx.replace_root_view(|cx| {
5591                let mut workspace =
5592                    Workspace::new(Some(workspace_id), project, app_state.clone(), cx);
5593
5594                workspace
5595                    .client()
5596                    .telemetry()
5597                    .report_app_event("open ssh project".to_string());
5598
5599                workspace.set_serialized_ssh_project(serialized_ssh_project);
5600                workspace
5601            });
5602        })?;
5603
5604        window
5605            .update(&mut cx, |_, cx| {
5606                cx.activate_window();
5607
5608                open_items(serialized_workspace, project_paths_to_open, cx)
5609            })?
5610            .await?;
5611
5612        window.update(&mut cx, |workspace, cx| {
5613            for error in project_path_errors {
5614                if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
5615                    if let Some(path) = error.error_tag("path") {
5616                        workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
5617                    }
5618                } else {
5619                    workspace.show_error(&error, cx)
5620                }
5621            }
5622        })
5623    })
5624}
5625
5626fn serialize_ssh_project(
5627    connection_options: SshConnectionOptions,
5628    paths: Vec<PathBuf>,
5629    cx: &AsyncAppContext,
5630) -> Task<
5631    Result<(
5632        SerializedSshProject,
5633        WorkspaceId,
5634        Option<SerializedWorkspace>,
5635    )>,
5636> {
5637    cx.background_executor().spawn(async move {
5638        let serialized_ssh_project = persistence::DB
5639            .get_or_create_ssh_project(
5640                connection_options.host.clone(),
5641                connection_options.port,
5642                paths
5643                    .iter()
5644                    .map(|path| path.to_string_lossy().to_string())
5645                    .collect::<Vec<_>>(),
5646                connection_options.username.clone(),
5647            )
5648            .await?;
5649
5650        let serialized_workspace =
5651            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
5652
5653        let workspace_id = if let Some(workspace_id) =
5654            serialized_workspace.as_ref().map(|workspace| workspace.id)
5655        {
5656            workspace_id
5657        } else {
5658            persistence::DB.next_id().await?
5659        };
5660
5661        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
5662    })
5663}
5664
5665pub fn join_in_room_project(
5666    project_id: u64,
5667    follow_user_id: u64,
5668    app_state: Arc<AppState>,
5669    cx: &mut AppContext,
5670) -> Task<Result<()>> {
5671    let windows = cx.windows();
5672    cx.spawn(|mut cx| async move {
5673        let existing_workspace = windows.into_iter().find_map(|window| {
5674            window.downcast::<Workspace>().and_then(|window| {
5675                window
5676                    .update(&mut cx, |workspace, cx| {
5677                        if workspace.project().read(cx).remote_id() == Some(project_id) {
5678                            Some(window)
5679                        } else {
5680                            None
5681                        }
5682                    })
5683                    .unwrap_or(None)
5684            })
5685        });
5686
5687        let workspace = if let Some(existing_workspace) = existing_workspace {
5688            existing_workspace
5689        } else {
5690            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
5691            let room = active_call
5692                .read_with(&cx, |call, _| call.room().cloned())?
5693                .ok_or_else(|| anyhow!("not in a call"))?;
5694            let project = room
5695                .update(&mut cx, |room, cx| {
5696                    room.join_project(
5697                        project_id,
5698                        app_state.languages.clone(),
5699                        app_state.fs.clone(),
5700                        cx,
5701                    )
5702                })?
5703                .await?;
5704
5705            let window_bounds_override = window_bounds_env_override();
5706            cx.update(|cx| {
5707                let mut options = (app_state.build_window_options)(None, cx);
5708                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
5709                cx.open_window(options, |cx| {
5710                    cx.new_view(|cx| {
5711                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5712                    })
5713                })
5714            })??
5715        };
5716
5717        workspace.update(&mut cx, |workspace, cx| {
5718            cx.activate(true);
5719            cx.activate_window();
5720
5721            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5722                let follow_peer_id = room
5723                    .read(cx)
5724                    .remote_participants()
5725                    .iter()
5726                    .find(|(_, participant)| participant.user.id == follow_user_id)
5727                    .map(|(_, p)| p.peer_id)
5728                    .or_else(|| {
5729                        // If we couldn't follow the given user, follow the host instead.
5730                        let collaborator = workspace
5731                            .project()
5732                            .read(cx)
5733                            .collaborators()
5734                            .values()
5735                            .find(|collaborator| collaborator.is_host)?;
5736                        Some(collaborator.peer_id)
5737                    });
5738
5739                if let Some(follow_peer_id) = follow_peer_id {
5740                    workspace.follow(follow_peer_id, cx);
5741                }
5742            }
5743        })?;
5744
5745        anyhow::Ok(())
5746    })
5747}
5748
5749pub fn reload(reload: &Reload, cx: &mut AppContext) {
5750    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5751    let mut workspace_windows = cx
5752        .windows()
5753        .into_iter()
5754        .filter_map(|window| window.downcast::<Workspace>())
5755        .collect::<Vec<_>>();
5756
5757    // If multiple windows have unsaved changes, and need a save prompt,
5758    // prompt in the active window before switching to a different window.
5759    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5760
5761    let mut prompt = None;
5762    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5763        prompt = window
5764            .update(cx, |_, cx| {
5765                cx.prompt(
5766                    PromptLevel::Info,
5767                    "Are you sure you want to restart?",
5768                    None,
5769                    &["Restart", "Cancel"],
5770                )
5771            })
5772            .ok();
5773    }
5774
5775    let binary_path = reload.binary_path.clone();
5776    cx.spawn(|mut cx| async move {
5777        if let Some(prompt) = prompt {
5778            let answer = prompt.await?;
5779            if answer != 0 {
5780                return Ok(());
5781            }
5782        }
5783
5784        // If the user cancels any save prompt, then keep the app open.
5785        for window in workspace_windows {
5786            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5787                workspace.prepare_to_close(CloseIntent::Quit, cx)
5788            }) {
5789                if !should_close.await? {
5790                    return Ok(());
5791                }
5792            }
5793        }
5794
5795        cx.update(|cx| cx.restart(binary_path))
5796    })
5797    .detach_and_log_err(cx);
5798}
5799
5800fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
5801    let mut parts = value.split(',');
5802    let x: usize = parts.next()?.parse().ok()?;
5803    let y: usize = parts.next()?.parse().ok()?;
5804    Some(point(px(x as f32), px(y as f32)))
5805}
5806
5807fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
5808    let mut parts = value.split(',');
5809    let width: usize = parts.next()?.parse().ok()?;
5810    let height: usize = parts.next()?.parse().ok()?;
5811    Some(size(px(width as f32), px(height as f32)))
5812}
5813
5814pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext) -> Stateful<Div> {
5815    const BORDER_SIZE: Pixels = px(1.0);
5816    let decorations = cx.window_decorations();
5817
5818    if matches!(decorations, Decorations::Client { .. }) {
5819        cx.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
5820    }
5821
5822    struct GlobalResizeEdge(ResizeEdge);
5823    impl Global for GlobalResizeEdge {}
5824
5825    div()
5826        .id("window-backdrop")
5827        .bg(transparent_black())
5828        .map(|div| match decorations {
5829            Decorations::Server => div,
5830            Decorations::Client { tiling, .. } => div
5831                .when(!(tiling.top || tiling.right), |div| {
5832                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5833                })
5834                .when(!(tiling.top || tiling.left), |div| {
5835                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5836                })
5837                .when(!(tiling.bottom || tiling.right), |div| {
5838                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5839                })
5840                .when(!(tiling.bottom || tiling.left), |div| {
5841                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5842                })
5843                .when(!tiling.top, |div| {
5844                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
5845                })
5846                .when(!tiling.bottom, |div| {
5847                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
5848                })
5849                .when(!tiling.left, |div| {
5850                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
5851                })
5852                .when(!tiling.right, |div| {
5853                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
5854                })
5855                .on_mouse_move(move |e, cx| {
5856                    let size = cx.window_bounds().get_bounds().size;
5857                    let pos = e.position;
5858
5859                    let new_edge =
5860                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
5861
5862                    let edge = cx.try_global::<GlobalResizeEdge>();
5863                    if new_edge != edge.map(|edge| edge.0) {
5864                        cx.window_handle()
5865                            .update(cx, |workspace, cx| cx.notify(workspace.entity_id()))
5866                            .ok();
5867                    }
5868                })
5869                .on_mouse_down(MouseButton::Left, move |e, cx| {
5870                    let size = cx.window_bounds().get_bounds().size;
5871                    let pos = e.position;
5872
5873                    let edge = match resize_edge(
5874                        pos,
5875                        theme::CLIENT_SIDE_DECORATION_SHADOW,
5876                        size,
5877                        tiling,
5878                    ) {
5879                        Some(value) => value,
5880                        None => return,
5881                    };
5882
5883                    cx.start_window_resize(edge);
5884                }),
5885        })
5886        .size_full()
5887        .child(
5888            div()
5889                .cursor(CursorStyle::Arrow)
5890                .map(|div| match decorations {
5891                    Decorations::Server => div,
5892                    Decorations::Client { tiling } => div
5893                        .border_color(cx.theme().colors().border)
5894                        .when(!(tiling.top || tiling.right), |div| {
5895                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5896                        })
5897                        .when(!(tiling.top || tiling.left), |div| {
5898                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5899                        })
5900                        .when(!(tiling.bottom || tiling.right), |div| {
5901                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5902                        })
5903                        .when(!(tiling.bottom || tiling.left), |div| {
5904                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5905                        })
5906                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
5907                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
5908                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
5909                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
5910                        .when(!tiling.is_tiled(), |div| {
5911                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
5912                                color: Hsla {
5913                                    h: 0.,
5914                                    s: 0.,
5915                                    l: 0.,
5916                                    a: 0.4,
5917                                },
5918                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
5919                                spread_radius: px(0.),
5920                                offset: point(px(0.0), px(0.0)),
5921                            }])
5922                        }),
5923                })
5924                .on_mouse_move(|_e, cx| {
5925                    cx.stop_propagation();
5926                })
5927                .size_full()
5928                .child(element),
5929        )
5930        .map(|div| match decorations {
5931            Decorations::Server => div,
5932            Decorations::Client { tiling, .. } => div.child(
5933                canvas(
5934                    |_bounds, cx| {
5935                        cx.insert_hitbox(
5936                            Bounds::new(
5937                                point(px(0.0), px(0.0)),
5938                                cx.window_bounds().get_bounds().size,
5939                            ),
5940                            false,
5941                        )
5942                    },
5943                    move |_bounds, hitbox, cx| {
5944                        let mouse = cx.mouse_position();
5945                        let size = cx.window_bounds().get_bounds().size;
5946                        let Some(edge) =
5947                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
5948                        else {
5949                            return;
5950                        };
5951                        cx.set_global(GlobalResizeEdge(edge));
5952                        cx.set_cursor_style(
5953                            match edge {
5954                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
5955                                ResizeEdge::Left | ResizeEdge::Right => {
5956                                    CursorStyle::ResizeLeftRight
5957                                }
5958                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
5959                                    CursorStyle::ResizeUpLeftDownRight
5960                                }
5961                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
5962                                    CursorStyle::ResizeUpRightDownLeft
5963                                }
5964                            },
5965                            &hitbox,
5966                        );
5967                    },
5968                )
5969                .size_full()
5970                .absolute(),
5971            ),
5972        })
5973}
5974
5975fn resize_edge(
5976    pos: Point<Pixels>,
5977    shadow_size: Pixels,
5978    window_size: Size<Pixels>,
5979    tiling: Tiling,
5980) -> Option<ResizeEdge> {
5981    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
5982    if bounds.contains(&pos) {
5983        return None;
5984    }
5985
5986    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
5987    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
5988    if !tiling.top && top_left_bounds.contains(&pos) {
5989        return Some(ResizeEdge::TopLeft);
5990    }
5991
5992    let top_right_bounds = Bounds::new(
5993        Point::new(window_size.width - corner_size.width, px(0.)),
5994        corner_size,
5995    );
5996    if !tiling.top && top_right_bounds.contains(&pos) {
5997        return Some(ResizeEdge::TopRight);
5998    }
5999
6000    let bottom_left_bounds = Bounds::new(
6001        Point::new(px(0.), window_size.height - corner_size.height),
6002        corner_size,
6003    );
6004    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
6005        return Some(ResizeEdge::BottomLeft);
6006    }
6007
6008    let bottom_right_bounds = Bounds::new(
6009        Point::new(
6010            window_size.width - corner_size.width,
6011            window_size.height - corner_size.height,
6012        ),
6013        corner_size,
6014    );
6015    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
6016        return Some(ResizeEdge::BottomRight);
6017    }
6018
6019    if !tiling.top && pos.y < shadow_size {
6020        Some(ResizeEdge::Top)
6021    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
6022        Some(ResizeEdge::Bottom)
6023    } else if !tiling.left && pos.x < shadow_size {
6024        Some(ResizeEdge::Left)
6025    } else if !tiling.right && pos.x > window_size.width - shadow_size {
6026        Some(ResizeEdge::Right)
6027    } else {
6028        None
6029    }
6030}
6031
6032fn join_pane_into_active(active_pane: &View<Pane>, pane: &View<Pane>, cx: &mut WindowContext<'_>) {
6033    if pane == active_pane {
6034        return;
6035    } else if pane.read(cx).items_len() == 0 {
6036        pane.update(cx, |_, cx| {
6037            cx.emit(pane::Event::Remove {
6038                focus_on_pane: None,
6039            });
6040        })
6041    } else {
6042        move_all_items(pane, active_pane, cx);
6043    }
6044}
6045
6046fn move_all_items(from_pane: &View<Pane>, to_pane: &View<Pane>, cx: &mut WindowContext<'_>) {
6047    let destination_is_different = from_pane != to_pane;
6048    let mut moved_items = 0;
6049    for (item_ix, item_handle) in from_pane
6050        .read(cx)
6051        .items()
6052        .enumerate()
6053        .map(|(ix, item)| (ix, item.clone()))
6054        .collect::<Vec<_>>()
6055    {
6056        let ix = item_ix - moved_items;
6057        if destination_is_different {
6058            // Close item from previous pane
6059            from_pane.update(cx, |source, cx| {
6060                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), cx);
6061            });
6062            moved_items += 1;
6063        }
6064
6065        // This automatically removes duplicate items in the pane
6066        to_pane.update(cx, |destination, cx| {
6067            destination.add_item(item_handle, true, true, None, cx);
6068            destination.focus(cx)
6069        });
6070    }
6071}
6072
6073pub fn move_item(
6074    source: &View<Pane>,
6075    destination: &View<Pane>,
6076    item_id_to_move: EntityId,
6077    destination_index: usize,
6078    cx: &mut WindowContext<'_>,
6079) {
6080    let Some((item_ix, item_handle)) = source
6081        .read(cx)
6082        .items()
6083        .enumerate()
6084        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
6085        .map(|(ix, item)| (ix, item.clone()))
6086    else {
6087        // Tab was closed during drag
6088        return;
6089    };
6090
6091    if source != destination {
6092        // Close item from previous pane
6093        source.update(cx, |source, cx| {
6094            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), cx);
6095        });
6096    }
6097
6098    // This automatically removes duplicate items in the pane
6099    destination.update(cx, |destination, cx| {
6100        destination.add_item(item_handle, true, true, Some(destination_index), cx);
6101        destination.focus(cx)
6102    });
6103}
6104
6105#[cfg(test)]
6106mod tests {
6107    use std::{cell::RefCell, rc::Rc};
6108
6109    use super::*;
6110    use crate::{
6111        dock::{test::TestPanel, PanelEvent},
6112        item::{
6113            test::{TestItem, TestProjectItem},
6114            ItemEvent,
6115        },
6116    };
6117    use fs::FakeFs;
6118    use gpui::{
6119        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
6120        UpdateGlobal, VisualTestContext,
6121    };
6122    use project::{Project, ProjectEntryId};
6123    use serde_json::json;
6124    use settings::SettingsStore;
6125
6126    #[gpui::test]
6127    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
6128        init_test(cx);
6129
6130        let fs = FakeFs::new(cx.executor());
6131        let project = Project::test(fs, [], cx).await;
6132        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6133
6134        // Adding an item with no ambiguity renders the tab without detail.
6135        let item1 = cx.new_view(|cx| {
6136            let mut item = TestItem::new(cx);
6137            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
6138            item
6139        });
6140        workspace.update(cx, |workspace, cx| {
6141            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6142        });
6143        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
6144
6145        // Adding an item that creates ambiguity increases the level of detail on
6146        // both tabs.
6147        let item2 = cx.new_view(|cx| {
6148            let mut item = TestItem::new(cx);
6149            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
6150            item
6151        });
6152        workspace.update(cx, |workspace, cx| {
6153            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6154        });
6155        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6156        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6157
6158        // Adding an item that creates ambiguity increases the level of detail only
6159        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
6160        // we stop at the highest detail available.
6161        let item3 = cx.new_view(|cx| {
6162            let mut item = TestItem::new(cx);
6163            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
6164            item
6165        });
6166        workspace.update(cx, |workspace, cx| {
6167            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6168        });
6169        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6170        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6171        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6172    }
6173
6174    #[gpui::test]
6175    async fn test_tracking_active_path(cx: &mut TestAppContext) {
6176        init_test(cx);
6177
6178        let fs = FakeFs::new(cx.executor());
6179        fs.insert_tree(
6180            "/root1",
6181            json!({
6182                "one.txt": "",
6183                "two.txt": "",
6184            }),
6185        )
6186        .await;
6187        fs.insert_tree(
6188            "/root2",
6189            json!({
6190                "three.txt": "",
6191            }),
6192        )
6193        .await;
6194
6195        let project = Project::test(fs, ["root1".as_ref()], cx).await;
6196        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6197        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6198        let worktree_id = project.update(cx, |project, cx| {
6199            project.worktrees(cx).next().unwrap().read(cx).id()
6200        });
6201
6202        let item1 = cx.new_view(|cx| {
6203            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
6204        });
6205        let item2 = cx.new_view(|cx| {
6206            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
6207        });
6208
6209        // Add an item to an empty pane
6210        workspace.update(cx, |workspace, cx| {
6211            workspace.add_item_to_active_pane(Box::new(item1), None, true, cx)
6212        });
6213        project.update(cx, |project, cx| {
6214            assert_eq!(
6215                project.active_entry(),
6216                project
6217                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6218                    .map(|e| e.id)
6219            );
6220        });
6221        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
6222
6223        // Add a second item to a non-empty pane
6224        workspace.update(cx, |workspace, cx| {
6225            workspace.add_item_to_active_pane(Box::new(item2), None, true, cx)
6226        });
6227        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
6228        project.update(cx, |project, cx| {
6229            assert_eq!(
6230                project.active_entry(),
6231                project
6232                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
6233                    .map(|e| e.id)
6234            );
6235        });
6236
6237        // Close the active item
6238        pane.update(cx, |pane, cx| {
6239            pane.close_active_item(&Default::default(), cx).unwrap()
6240        })
6241        .await
6242        .unwrap();
6243        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
6244        project.update(cx, |project, cx| {
6245            assert_eq!(
6246                project.active_entry(),
6247                project
6248                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6249                    .map(|e| e.id)
6250            );
6251        });
6252
6253        // Add a project folder
6254        project
6255            .update(cx, |project, cx| {
6256                project.find_or_create_worktree("root2", true, cx)
6257            })
6258            .await
6259            .unwrap();
6260        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
6261
6262        // Remove a project folder
6263        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
6264        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
6265    }
6266
6267    #[gpui::test]
6268    async fn test_close_window(cx: &mut TestAppContext) {
6269        init_test(cx);
6270
6271        let fs = FakeFs::new(cx.executor());
6272        fs.insert_tree("/root", json!({ "one": "" })).await;
6273
6274        let project = Project::test(fs, ["root".as_ref()], cx).await;
6275        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6276
6277        // When there are no dirty items, there's nothing to do.
6278        let item1 = cx.new_view(TestItem::new);
6279        workspace.update(cx, |w, cx| {
6280            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx)
6281        });
6282        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6283        assert!(task.await.unwrap());
6284
6285        // When there are dirty untitled items, prompt to save each one. If the user
6286        // cancels any prompt, then abort.
6287        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
6288        let item3 = cx.new_view(|cx| {
6289            TestItem::new(cx)
6290                .with_dirty(true)
6291                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6292        });
6293        workspace.update(cx, |w, cx| {
6294            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6295            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6296        });
6297        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6298        cx.executor().run_until_parked();
6299        cx.simulate_prompt_answer(2); // cancel save all
6300        cx.executor().run_until_parked();
6301        cx.simulate_prompt_answer(2); // cancel save all
6302        cx.executor().run_until_parked();
6303        assert!(!cx.has_pending_prompt());
6304        assert!(!task.await.unwrap());
6305    }
6306
6307    #[gpui::test]
6308    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
6309        init_test(cx);
6310
6311        // Register TestItem as a serializable item
6312        cx.update(|cx| {
6313            register_serializable_item::<TestItem>(cx);
6314        });
6315
6316        let fs = FakeFs::new(cx.executor());
6317        fs.insert_tree("/root", json!({ "one": "" })).await;
6318
6319        let project = Project::test(fs, ["root".as_ref()], cx).await;
6320        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6321
6322        // When there are dirty untitled items, but they can serialize, then there is no prompt.
6323        let item1 = cx.new_view(|cx| {
6324            TestItem::new(cx)
6325                .with_dirty(true)
6326                .with_serialize(|| Some(Task::ready(Ok(()))))
6327        });
6328        let item2 = cx.new_view(|cx| {
6329            TestItem::new(cx)
6330                .with_dirty(true)
6331                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6332                .with_serialize(|| Some(Task::ready(Ok(()))))
6333        });
6334        workspace.update(cx, |w, cx| {
6335            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6336            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6337        });
6338        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6339        assert!(task.await.unwrap());
6340    }
6341
6342    #[gpui::test]
6343    async fn test_close_pane_items(cx: &mut TestAppContext) {
6344        init_test(cx);
6345
6346        let fs = FakeFs::new(cx.executor());
6347
6348        let project = Project::test(fs, None, cx).await;
6349        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6350
6351        let item1 = cx.new_view(|cx| {
6352            TestItem::new(cx)
6353                .with_dirty(true)
6354                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6355        });
6356        let item2 = cx.new_view(|cx| {
6357            TestItem::new(cx)
6358                .with_dirty(true)
6359                .with_conflict(true)
6360                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
6361        });
6362        let item3 = cx.new_view(|cx| {
6363            TestItem::new(cx)
6364                .with_dirty(true)
6365                .with_conflict(true)
6366                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
6367        });
6368        let item4 = cx.new_view(|cx| {
6369            TestItem::new(cx)
6370                .with_dirty(true)
6371                .with_project_items(&[TestProjectItem::new_untitled(cx)])
6372        });
6373        let pane = workspace.update(cx, |workspace, cx| {
6374            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6375            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6376            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6377            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, cx);
6378            workspace.active_pane().clone()
6379        });
6380
6381        let close_items = pane.update(cx, |pane, cx| {
6382            pane.activate_item(1, true, true, cx);
6383            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6384            let item1_id = item1.item_id();
6385            let item3_id = item3.item_id();
6386            let item4_id = item4.item_id();
6387            pane.close_items(cx, SaveIntent::Close, move |id| {
6388                [item1_id, item3_id, item4_id].contains(&id)
6389            })
6390        });
6391        cx.executor().run_until_parked();
6392
6393        assert!(cx.has_pending_prompt());
6394        // Ignore "Save all" prompt
6395        cx.simulate_prompt_answer(2);
6396        cx.executor().run_until_parked();
6397        // There's a prompt to save item 1.
6398        pane.update(cx, |pane, _| {
6399            assert_eq!(pane.items_len(), 4);
6400            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
6401        });
6402        // Confirm saving item 1.
6403        cx.simulate_prompt_answer(0);
6404        cx.executor().run_until_parked();
6405
6406        // Item 1 is saved. There's a prompt to save item 3.
6407        pane.update(cx, |pane, cx| {
6408            assert_eq!(item1.read(cx).save_count, 1);
6409            assert_eq!(item1.read(cx).save_as_count, 0);
6410            assert_eq!(item1.read(cx).reload_count, 0);
6411            assert_eq!(pane.items_len(), 3);
6412            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
6413        });
6414        assert!(cx.has_pending_prompt());
6415
6416        // Cancel saving item 3.
6417        cx.simulate_prompt_answer(1);
6418        cx.executor().run_until_parked();
6419
6420        // Item 3 is reloaded. There's a prompt to save item 4.
6421        pane.update(cx, |pane, cx| {
6422            assert_eq!(item3.read(cx).save_count, 0);
6423            assert_eq!(item3.read(cx).save_as_count, 0);
6424            assert_eq!(item3.read(cx).reload_count, 1);
6425            assert_eq!(pane.items_len(), 2);
6426            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
6427        });
6428        assert!(cx.has_pending_prompt());
6429
6430        // Confirm saving item 4.
6431        cx.simulate_prompt_answer(0);
6432        cx.executor().run_until_parked();
6433
6434        // There's a prompt for a path for item 4.
6435        cx.simulate_new_path_selection(|_| Some(Default::default()));
6436        close_items.await.unwrap();
6437
6438        // The requested items are closed.
6439        pane.update(cx, |pane, cx| {
6440            assert_eq!(item4.read(cx).save_count, 0);
6441            assert_eq!(item4.read(cx).save_as_count, 1);
6442            assert_eq!(item4.read(cx).reload_count, 0);
6443            assert_eq!(pane.items_len(), 1);
6444            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6445        });
6446    }
6447
6448    #[gpui::test]
6449    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
6450        init_test(cx);
6451
6452        let fs = FakeFs::new(cx.executor());
6453        let project = Project::test(fs, [], cx).await;
6454        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6455
6456        // Create several workspace items with single project entries, and two
6457        // workspace items with multiple project entries.
6458        let single_entry_items = (0..=4)
6459            .map(|project_entry_id| {
6460                cx.new_view(|cx| {
6461                    TestItem::new(cx)
6462                        .with_dirty(true)
6463                        .with_project_items(&[TestProjectItem::new(
6464                            project_entry_id,
6465                            &format!("{project_entry_id}.txt"),
6466                            cx,
6467                        )])
6468                })
6469            })
6470            .collect::<Vec<_>>();
6471        let item_2_3 = cx.new_view(|cx| {
6472            TestItem::new(cx)
6473                .with_dirty(true)
6474                .with_singleton(false)
6475                .with_project_items(&[
6476                    single_entry_items[2].read(cx).project_items[0].clone(),
6477                    single_entry_items[3].read(cx).project_items[0].clone(),
6478                ])
6479        });
6480        let item_3_4 = cx.new_view(|cx| {
6481            TestItem::new(cx)
6482                .with_dirty(true)
6483                .with_singleton(false)
6484                .with_project_items(&[
6485                    single_entry_items[3].read(cx).project_items[0].clone(),
6486                    single_entry_items[4].read(cx).project_items[0].clone(),
6487                ])
6488        });
6489
6490        // Create two panes that contain the following project entries:
6491        //   left pane:
6492        //     multi-entry items:   (2, 3)
6493        //     single-entry items:  0, 1, 2, 3, 4
6494        //   right pane:
6495        //     single-entry items:  1
6496        //     multi-entry items:   (3, 4)
6497        let left_pane = workspace.update(cx, |workspace, cx| {
6498            let left_pane = workspace.active_pane().clone();
6499            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, cx);
6500            for item in single_entry_items {
6501                workspace.add_item_to_active_pane(Box::new(item), None, true, cx);
6502            }
6503            left_pane.update(cx, |pane, cx| {
6504                pane.activate_item(2, true, true, cx);
6505            });
6506
6507            let right_pane = workspace
6508                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
6509                .unwrap();
6510
6511            right_pane.update(cx, |pane, cx| {
6512                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
6513            });
6514
6515            left_pane
6516        });
6517
6518        cx.focus_view(&left_pane);
6519
6520        // When closing all of the items in the left pane, we should be prompted twice:
6521        // once for project entry 0, and once for project entry 2. Project entries 1,
6522        // 3, and 4 are all still open in the other paten. After those two
6523        // prompts, the task should complete.
6524
6525        let close = left_pane.update(cx, |pane, cx| {
6526            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
6527        });
6528        cx.executor().run_until_parked();
6529
6530        // Discard "Save all" prompt
6531        cx.simulate_prompt_answer(2);
6532
6533        cx.executor().run_until_parked();
6534        left_pane.update(cx, |pane, cx| {
6535            assert_eq!(
6536                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6537                &[ProjectEntryId::from_proto(0)]
6538            );
6539        });
6540        cx.simulate_prompt_answer(0);
6541
6542        cx.executor().run_until_parked();
6543        left_pane.update(cx, |pane, cx| {
6544            assert_eq!(
6545                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6546                &[ProjectEntryId::from_proto(2)]
6547            );
6548        });
6549        cx.simulate_prompt_answer(0);
6550
6551        cx.executor().run_until_parked();
6552        close.await.unwrap();
6553        left_pane.update(cx, |pane, _| {
6554            assert_eq!(pane.items_len(), 0);
6555        });
6556    }
6557
6558    #[gpui::test]
6559    async fn test_autosave(cx: &mut gpui::TestAppContext) {
6560        init_test(cx);
6561
6562        let fs = FakeFs::new(cx.executor());
6563        let project = Project::test(fs, [], cx).await;
6564        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6565        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6566
6567        let item = cx.new_view(|cx| {
6568            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6569        });
6570        let item_id = item.entity_id();
6571        workspace.update(cx, |workspace, cx| {
6572            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6573        });
6574
6575        // Autosave on window change.
6576        item.update(cx, |item, cx| {
6577            SettingsStore::update_global(cx, |settings, cx| {
6578                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6579                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
6580                })
6581            });
6582            item.is_dirty = true;
6583        });
6584
6585        // Deactivating the window saves the file.
6586        cx.deactivate_window();
6587        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6588
6589        // Re-activating the window doesn't save the file.
6590        cx.update(|cx| cx.activate_window());
6591        cx.executor().run_until_parked();
6592        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6593
6594        // Autosave on focus change.
6595        item.update(cx, |item, cx| {
6596            cx.focus_self();
6597            SettingsStore::update_global(cx, |settings, cx| {
6598                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6599                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6600                })
6601            });
6602            item.is_dirty = true;
6603        });
6604
6605        // Blurring the item saves the file.
6606        item.update(cx, |_, cx| cx.blur());
6607        cx.executor().run_until_parked();
6608        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
6609
6610        // Deactivating the window still saves the file.
6611        item.update(cx, |item, cx| {
6612            cx.focus_self();
6613            item.is_dirty = true;
6614        });
6615        cx.deactivate_window();
6616        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6617
6618        // Autosave after delay.
6619        item.update(cx, |item, cx| {
6620            SettingsStore::update_global(cx, |settings, cx| {
6621                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6622                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
6623                })
6624            });
6625            item.is_dirty = true;
6626            cx.emit(ItemEvent::Edit);
6627        });
6628
6629        // Delay hasn't fully expired, so the file is still dirty and unsaved.
6630        cx.executor().advance_clock(Duration::from_millis(250));
6631        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6632
6633        // After delay expires, the file is saved.
6634        cx.executor().advance_clock(Duration::from_millis(250));
6635        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
6636
6637        // Autosave on focus change, ensuring closing the tab counts as such.
6638        item.update(cx, |item, cx| {
6639            SettingsStore::update_global(cx, |settings, cx| {
6640                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6641                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6642                })
6643            });
6644            item.is_dirty = true;
6645        });
6646
6647        pane.update(cx, |pane, cx| {
6648            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6649        })
6650        .await
6651        .unwrap();
6652        assert!(!cx.has_pending_prompt());
6653        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6654
6655        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
6656        workspace.update(cx, |workspace, cx| {
6657            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6658        });
6659        item.update(cx, |item, cx| {
6660            item.project_items[0].update(cx, |item, _| {
6661                item.entry_id = None;
6662            });
6663            item.is_dirty = true;
6664            cx.blur();
6665        });
6666        cx.run_until_parked();
6667        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6668
6669        // Ensure autosave is prevented for deleted files also when closing the buffer.
6670        let _close_items = pane.update(cx, |pane, cx| {
6671            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6672        });
6673        cx.run_until_parked();
6674        assert!(cx.has_pending_prompt());
6675        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6676    }
6677
6678    #[gpui::test]
6679    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
6680        init_test(cx);
6681
6682        let fs = FakeFs::new(cx.executor());
6683
6684        let project = Project::test(fs, [], cx).await;
6685        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6686
6687        let item = cx.new_view(|cx| {
6688            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6689        });
6690        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6691        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
6692        let toolbar_notify_count = Rc::new(RefCell::new(0));
6693
6694        workspace.update(cx, |workspace, cx| {
6695            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6696            let toolbar_notification_count = toolbar_notify_count.clone();
6697            cx.observe(&toolbar, move |_, _, _| {
6698                *toolbar_notification_count.borrow_mut() += 1
6699            })
6700            .detach();
6701        });
6702
6703        pane.update(cx, |pane, _| {
6704            assert!(!pane.can_navigate_backward());
6705            assert!(!pane.can_navigate_forward());
6706        });
6707
6708        item.update(cx, |item, cx| {
6709            item.set_state("one".to_string(), cx);
6710        });
6711
6712        // Toolbar must be notified to re-render the navigation buttons
6713        assert_eq!(*toolbar_notify_count.borrow(), 1);
6714
6715        pane.update(cx, |pane, _| {
6716            assert!(pane.can_navigate_backward());
6717            assert!(!pane.can_navigate_forward());
6718        });
6719
6720        workspace
6721            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
6722            .await
6723            .unwrap();
6724
6725        assert_eq!(*toolbar_notify_count.borrow(), 2);
6726        pane.update(cx, |pane, _| {
6727            assert!(!pane.can_navigate_backward());
6728            assert!(pane.can_navigate_forward());
6729        });
6730    }
6731
6732    #[gpui::test]
6733    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
6734        init_test(cx);
6735        let fs = FakeFs::new(cx.executor());
6736
6737        let project = Project::test(fs, [], cx).await;
6738        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6739
6740        let panel = workspace.update(cx, |workspace, cx| {
6741            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6742            workspace.add_panel(panel.clone(), cx);
6743
6744            workspace
6745                .right_dock()
6746                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6747
6748            panel
6749        });
6750
6751        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6752        pane.update(cx, |pane, cx| {
6753            let item = cx.new_view(TestItem::new);
6754            pane.add_item(Box::new(item), true, true, None, cx);
6755        });
6756
6757        // Transfer focus from center to panel
6758        workspace.update(cx, |workspace, cx| {
6759            workspace.toggle_panel_focus::<TestPanel>(cx);
6760        });
6761
6762        workspace.update(cx, |workspace, cx| {
6763            assert!(workspace.right_dock().read(cx).is_open());
6764            assert!(!panel.is_zoomed(cx));
6765            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6766        });
6767
6768        // Transfer focus from panel to center
6769        workspace.update(cx, |workspace, cx| {
6770            workspace.toggle_panel_focus::<TestPanel>(cx);
6771        });
6772
6773        workspace.update(cx, |workspace, cx| {
6774            assert!(workspace.right_dock().read(cx).is_open());
6775            assert!(!panel.is_zoomed(cx));
6776            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6777        });
6778
6779        // Close the dock
6780        workspace.update(cx, |workspace, cx| {
6781            workspace.toggle_dock(DockPosition::Right, cx);
6782        });
6783
6784        workspace.update(cx, |workspace, cx| {
6785            assert!(!workspace.right_dock().read(cx).is_open());
6786            assert!(!panel.is_zoomed(cx));
6787            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6788        });
6789
6790        // Open the dock
6791        workspace.update(cx, |workspace, cx| {
6792            workspace.toggle_dock(DockPosition::Right, cx);
6793        });
6794
6795        workspace.update(cx, |workspace, cx| {
6796            assert!(workspace.right_dock().read(cx).is_open());
6797            assert!(!panel.is_zoomed(cx));
6798            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6799        });
6800
6801        // Focus and zoom panel
6802        panel.update(cx, |panel, cx| {
6803            cx.focus_self();
6804            panel.set_zoomed(true, cx)
6805        });
6806
6807        workspace.update(cx, |workspace, cx| {
6808            assert!(workspace.right_dock().read(cx).is_open());
6809            assert!(panel.is_zoomed(cx));
6810            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6811        });
6812
6813        // Transfer focus to the center closes the dock
6814        workspace.update(cx, |workspace, cx| {
6815            workspace.toggle_panel_focus::<TestPanel>(cx);
6816        });
6817
6818        workspace.update(cx, |workspace, cx| {
6819            assert!(!workspace.right_dock().read(cx).is_open());
6820            assert!(panel.is_zoomed(cx));
6821            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6822        });
6823
6824        // Transferring focus back to the panel keeps it zoomed
6825        workspace.update(cx, |workspace, cx| {
6826            workspace.toggle_panel_focus::<TestPanel>(cx);
6827        });
6828
6829        workspace.update(cx, |workspace, cx| {
6830            assert!(workspace.right_dock().read(cx).is_open());
6831            assert!(panel.is_zoomed(cx));
6832            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6833        });
6834
6835        // Close the dock while it is zoomed
6836        workspace.update(cx, |workspace, cx| {
6837            workspace.toggle_dock(DockPosition::Right, cx)
6838        });
6839
6840        workspace.update(cx, |workspace, cx| {
6841            assert!(!workspace.right_dock().read(cx).is_open());
6842            assert!(panel.is_zoomed(cx));
6843            assert!(workspace.zoomed.is_none());
6844            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6845        });
6846
6847        // Opening the dock, when it's zoomed, retains focus
6848        workspace.update(cx, |workspace, cx| {
6849            workspace.toggle_dock(DockPosition::Right, cx)
6850        });
6851
6852        workspace.update(cx, |workspace, cx| {
6853            assert!(workspace.right_dock().read(cx).is_open());
6854            assert!(panel.is_zoomed(cx));
6855            assert!(workspace.zoomed.is_some());
6856            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6857        });
6858
6859        // Unzoom and close the panel, zoom the active pane.
6860        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
6861        workspace.update(cx, |workspace, cx| {
6862            workspace.toggle_dock(DockPosition::Right, cx)
6863        });
6864        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
6865
6866        // Opening a dock unzooms the pane.
6867        workspace.update(cx, |workspace, cx| {
6868            workspace.toggle_dock(DockPosition::Right, cx)
6869        });
6870        workspace.update(cx, |workspace, cx| {
6871            let pane = pane.read(cx);
6872            assert!(!pane.is_zoomed());
6873            assert!(!pane.focus_handle(cx).is_focused(cx));
6874            assert!(workspace.right_dock().read(cx).is_open());
6875            assert!(workspace.zoomed.is_none());
6876        });
6877    }
6878
6879    #[gpui::test]
6880    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
6881        init_test(cx);
6882
6883        let fs = FakeFs::new(cx.executor());
6884
6885        let project = Project::test(fs, None, cx).await;
6886        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6887
6888        // Let's arrange the panes like this:
6889        //
6890        // +-----------------------+
6891        // |         top           |
6892        // +------+--------+-------+
6893        // | left | center | right |
6894        // +------+--------+-------+
6895        // |        bottom         |
6896        // +-----------------------+
6897
6898        let top_item = cx.new_view(|cx| {
6899            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
6900        });
6901        let bottom_item = cx.new_view(|cx| {
6902            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
6903        });
6904        let left_item = cx.new_view(|cx| {
6905            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
6906        });
6907        let right_item = cx.new_view(|cx| {
6908            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
6909        });
6910        let center_item = cx.new_view(|cx| {
6911            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
6912        });
6913
6914        let top_pane_id = workspace.update(cx, |workspace, cx| {
6915            let top_pane_id = workspace.active_pane().entity_id();
6916            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, cx);
6917            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Down, cx);
6918            top_pane_id
6919        });
6920        let bottom_pane_id = workspace.update(cx, |workspace, cx| {
6921            let bottom_pane_id = workspace.active_pane().entity_id();
6922            workspace.add_item_to_active_pane(Box::new(bottom_item.clone()), None, false, cx);
6923            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Up, cx);
6924            bottom_pane_id
6925        });
6926        let left_pane_id = workspace.update(cx, |workspace, cx| {
6927            let left_pane_id = workspace.active_pane().entity_id();
6928            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, cx);
6929            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
6930            left_pane_id
6931        });
6932        let right_pane_id = workspace.update(cx, |workspace, cx| {
6933            let right_pane_id = workspace.active_pane().entity_id();
6934            workspace.add_item_to_active_pane(Box::new(right_item.clone()), None, false, cx);
6935            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Left, cx);
6936            right_pane_id
6937        });
6938        let center_pane_id = workspace.update(cx, |workspace, cx| {
6939            let center_pane_id = workspace.active_pane().entity_id();
6940            workspace.add_item_to_active_pane(Box::new(center_item.clone()), None, false, cx);
6941            center_pane_id
6942        });
6943        cx.executor().run_until_parked();
6944
6945        workspace.update(cx, |workspace, cx| {
6946            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
6947
6948            // Join into next from center pane into right
6949            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6950        });
6951
6952        workspace.update(cx, |workspace, cx| {
6953            let active_pane = workspace.active_pane();
6954            assert_eq!(right_pane_id, active_pane.entity_id());
6955            assert_eq!(2, active_pane.read(cx).items_len());
6956            let item_ids_in_pane =
6957                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6958            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6959            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6960
6961            // Join into next from right pane into bottom
6962            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6963        });
6964
6965        workspace.update(cx, |workspace, cx| {
6966            let active_pane = workspace.active_pane();
6967            assert_eq!(bottom_pane_id, active_pane.entity_id());
6968            assert_eq!(3, active_pane.read(cx).items_len());
6969            let item_ids_in_pane =
6970                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6971            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6972            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6973            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
6974
6975            // Join into next from bottom pane into left
6976            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6977        });
6978
6979        workspace.update(cx, |workspace, cx| {
6980            let active_pane = workspace.active_pane();
6981            assert_eq!(left_pane_id, active_pane.entity_id());
6982            assert_eq!(4, active_pane.read(cx).items_len());
6983            let item_ids_in_pane =
6984                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6985            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6986            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6987            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
6988            assert!(item_ids_in_pane.contains(&left_item.item_id()));
6989
6990            // Join into next from left pane into top
6991            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6992        });
6993
6994        workspace.update(cx, |workspace, cx| {
6995            let active_pane = workspace.active_pane();
6996            assert_eq!(top_pane_id, active_pane.entity_id());
6997            assert_eq!(5, active_pane.read(cx).items_len());
6998            let item_ids_in_pane =
6999                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
7000            assert!(item_ids_in_pane.contains(&center_item.item_id()));
7001            assert!(item_ids_in_pane.contains(&right_item.item_id()));
7002            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
7003            assert!(item_ids_in_pane.contains(&left_item.item_id()));
7004            assert!(item_ids_in_pane.contains(&top_item.item_id()));
7005
7006            // Single pane left: no-op
7007            workspace.join_pane_into_next(workspace.active_pane().clone(), cx)
7008        });
7009
7010        workspace.update(cx, |workspace, _cx| {
7011            let active_pane = workspace.active_pane();
7012            assert_eq!(top_pane_id, active_pane.entity_id());
7013        });
7014    }
7015
7016    fn add_an_item_to_active_pane(
7017        cx: &mut VisualTestContext,
7018        workspace: &View<Workspace>,
7019        item_id: u64,
7020    ) -> View<TestItem> {
7021        let item = cx.new_view(|cx| {
7022            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
7023                item_id,
7024                "item{item_id}.txt",
7025                cx,
7026            )])
7027        });
7028        workspace.update(cx, |workspace, cx| {
7029            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, cx);
7030        });
7031        return item;
7032    }
7033
7034    fn split_pane(cx: &mut VisualTestContext, workspace: &View<Workspace>) -> View<Pane> {
7035        return workspace.update(cx, |workspace, cx| {
7036            let new_pane =
7037                workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
7038            new_pane
7039        });
7040    }
7041
7042    #[gpui::test]
7043    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
7044        init_test(cx);
7045        let fs = FakeFs::new(cx.executor());
7046        let project = Project::test(fs, None, cx).await;
7047        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
7048
7049        add_an_item_to_active_pane(cx, &workspace, 1);
7050        split_pane(cx, &workspace);
7051        add_an_item_to_active_pane(cx, &workspace, 2);
7052        split_pane(cx, &workspace); // empty pane
7053        split_pane(cx, &workspace);
7054        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
7055
7056        cx.executor().run_until_parked();
7057
7058        workspace.update(cx, |workspace, cx| {
7059            let num_panes = workspace.panes().len();
7060            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
7061            let active_item = workspace
7062                .active_pane()
7063                .read(cx)
7064                .active_item()
7065                .expect("item is in focus");
7066
7067            assert_eq!(num_panes, 4);
7068            assert_eq!(num_items_in_current_pane, 1);
7069            assert_eq!(active_item.item_id(), last_item.item_id());
7070        });
7071
7072        workspace.update(cx, |workspace, cx| {
7073            workspace.join_all_panes(cx);
7074        });
7075
7076        workspace.update(cx, |workspace, cx| {
7077            let num_panes = workspace.panes().len();
7078            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
7079            let active_item = workspace
7080                .active_pane()
7081                .read(cx)
7082                .active_item()
7083                .expect("item is in focus");
7084
7085            assert_eq!(num_panes, 1);
7086            assert_eq!(num_items_in_current_pane, 3);
7087            assert_eq!(active_item.item_id(), last_item.item_id());
7088        });
7089    }
7090    struct TestModal(FocusHandle);
7091
7092    impl TestModal {
7093        fn new(cx: &mut ViewContext<Self>) -> Self {
7094            Self(cx.focus_handle())
7095        }
7096    }
7097
7098    impl EventEmitter<DismissEvent> for TestModal {}
7099
7100    impl FocusableView for TestModal {
7101        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7102            self.0.clone()
7103        }
7104    }
7105
7106    impl ModalView for TestModal {}
7107
7108    impl Render for TestModal {
7109        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
7110            div().track_focus(&self.0)
7111        }
7112    }
7113
7114    #[gpui::test]
7115    async fn test_panels(cx: &mut gpui::TestAppContext) {
7116        init_test(cx);
7117        let fs = FakeFs::new(cx.executor());
7118
7119        let project = Project::test(fs, [], cx).await;
7120        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
7121
7122        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
7123            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
7124            workspace.add_panel(panel_1.clone(), cx);
7125            workspace
7126                .left_dock()
7127                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
7128            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
7129            workspace.add_panel(panel_2.clone(), cx);
7130            workspace
7131                .right_dock()
7132                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
7133
7134            let left_dock = workspace.left_dock();
7135            assert_eq!(
7136                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7137                panel_1.panel_id()
7138            );
7139            assert_eq!(
7140                left_dock.read(cx).active_panel_size(cx).unwrap(),
7141                panel_1.size(cx)
7142            );
7143
7144            left_dock.update(cx, |left_dock, cx| {
7145                left_dock.resize_active_panel(Some(px(1337.)), cx)
7146            });
7147            assert_eq!(
7148                workspace
7149                    .right_dock()
7150                    .read(cx)
7151                    .visible_panel()
7152                    .unwrap()
7153                    .panel_id(),
7154                panel_2.panel_id(),
7155            );
7156
7157            (panel_1, panel_2)
7158        });
7159
7160        // Move panel_1 to the right
7161        panel_1.update(cx, |panel_1, cx| {
7162            panel_1.set_position(DockPosition::Right, cx)
7163        });
7164
7165        workspace.update(cx, |workspace, cx| {
7166            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
7167            // Since it was the only panel on the left, the left dock should now be closed.
7168            assert!(!workspace.left_dock().read(cx).is_open());
7169            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
7170            let right_dock = workspace.right_dock();
7171            assert_eq!(
7172                right_dock.read(cx).visible_panel().unwrap().panel_id(),
7173                panel_1.panel_id()
7174            );
7175            assert_eq!(
7176                right_dock.read(cx).active_panel_size(cx).unwrap(),
7177                px(1337.)
7178            );
7179
7180            // Now we move panel_2 to the left
7181            panel_2.set_position(DockPosition::Left, cx);
7182        });
7183
7184        workspace.update(cx, |workspace, cx| {
7185            // Since panel_2 was not visible on the right, we don't open the left dock.
7186            assert!(!workspace.left_dock().read(cx).is_open());
7187            // And the right dock is unaffected in its displaying of panel_1
7188            assert!(workspace.right_dock().read(cx).is_open());
7189            assert_eq!(
7190                workspace
7191                    .right_dock()
7192                    .read(cx)
7193                    .visible_panel()
7194                    .unwrap()
7195                    .panel_id(),
7196                panel_1.panel_id(),
7197            );
7198        });
7199
7200        // Move panel_1 back to the left
7201        panel_1.update(cx, |panel_1, cx| {
7202            panel_1.set_position(DockPosition::Left, cx)
7203        });
7204
7205        workspace.update(cx, |workspace, cx| {
7206            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
7207            let left_dock = workspace.left_dock();
7208            assert!(left_dock.read(cx).is_open());
7209            assert_eq!(
7210                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7211                panel_1.panel_id()
7212            );
7213            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
7214            // And the right dock should be closed as it no longer has any panels.
7215            assert!(!workspace.right_dock().read(cx).is_open());
7216
7217            // Now we move panel_1 to the bottom
7218            panel_1.set_position(DockPosition::Bottom, cx);
7219        });
7220
7221        workspace.update(cx, |workspace, cx| {
7222            // Since panel_1 was visible on the left, we close the left dock.
7223            assert!(!workspace.left_dock().read(cx).is_open());
7224            // The bottom dock is sized based on the panel's default size,
7225            // since the panel orientation changed from vertical to horizontal.
7226            let bottom_dock = workspace.bottom_dock();
7227            assert_eq!(
7228                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
7229                panel_1.size(cx),
7230            );
7231            // Close bottom dock and move panel_1 back to the left.
7232            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
7233            panel_1.set_position(DockPosition::Left, cx);
7234        });
7235
7236        // Emit activated event on panel 1
7237        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
7238
7239        // Now the left dock is open and panel_1 is active and focused.
7240        workspace.update(cx, |workspace, cx| {
7241            let left_dock = workspace.left_dock();
7242            assert!(left_dock.read(cx).is_open());
7243            assert_eq!(
7244                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7245                panel_1.panel_id(),
7246            );
7247            assert!(panel_1.focus_handle(cx).is_focused(cx));
7248        });
7249
7250        // Emit closed event on panel 2, which is not active
7251        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7252
7253        // Wo don't close the left dock, because panel_2 wasn't the active panel
7254        workspace.update(cx, |workspace, cx| {
7255            let left_dock = workspace.left_dock();
7256            assert!(left_dock.read(cx).is_open());
7257            assert_eq!(
7258                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7259                panel_1.panel_id(),
7260            );
7261        });
7262
7263        // Emitting a ZoomIn event shows the panel as zoomed.
7264        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
7265        workspace.update(cx, |workspace, _| {
7266            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7267            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
7268        });
7269
7270        // Move panel to another dock while it is zoomed
7271        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
7272        workspace.update(cx, |workspace, _| {
7273            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7274
7275            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7276        });
7277
7278        // This is a helper for getting a:
7279        // - valid focus on an element,
7280        // - that isn't a part of the panes and panels system of the Workspace,
7281        // - and doesn't trigger the 'on_focus_lost' API.
7282        let focus_other_view = {
7283            let workspace = workspace.clone();
7284            move |cx: &mut VisualTestContext| {
7285                workspace.update(cx, |workspace, cx| {
7286                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
7287                        workspace.toggle_modal(cx, TestModal::new);
7288                        workspace.toggle_modal(cx, TestModal::new);
7289                    } else {
7290                        workspace.toggle_modal(cx, TestModal::new);
7291                    }
7292                })
7293            }
7294        };
7295
7296        // If focus is transferred to another view that's not a panel or another pane, we still show
7297        // the panel as zoomed.
7298        focus_other_view(cx);
7299        workspace.update(cx, |workspace, _| {
7300            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7301            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7302        });
7303
7304        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
7305        workspace.update(cx, |_, cx| cx.focus_self());
7306        workspace.update(cx, |workspace, _| {
7307            assert_eq!(workspace.zoomed, None);
7308            assert_eq!(workspace.zoomed_position, None);
7309        });
7310
7311        // If focus is transferred again to another view that's not a panel or a pane, we won't
7312        // show the panel as zoomed because it wasn't zoomed before.
7313        focus_other_view(cx);
7314        workspace.update(cx, |workspace, _| {
7315            assert_eq!(workspace.zoomed, None);
7316            assert_eq!(workspace.zoomed_position, None);
7317        });
7318
7319        // When the panel is activated, it is zoomed again.
7320        cx.dispatch_action(ToggleRightDock);
7321        workspace.update(cx, |workspace, _| {
7322            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7323            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7324        });
7325
7326        // Emitting a ZoomOut event unzooms the panel.
7327        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
7328        workspace.update(cx, |workspace, _| {
7329            assert_eq!(workspace.zoomed, None);
7330            assert_eq!(workspace.zoomed_position, None);
7331        });
7332
7333        // Emit closed event on panel 1, which is active
7334        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7335
7336        // Now the left dock is closed, because panel_1 was the active panel
7337        workspace.update(cx, |workspace, cx| {
7338            let right_dock = workspace.right_dock();
7339            assert!(!right_dock.read(cx).is_open());
7340        });
7341    }
7342
7343    mod register_project_item_tests {
7344        use ui::Context as _;
7345
7346        use super::*;
7347
7348        // View
7349        struct TestPngItemView {
7350            focus_handle: FocusHandle,
7351        }
7352        // Model
7353        struct TestPngItem {}
7354
7355        impl project::Item for TestPngItem {
7356            fn try_open(
7357                _project: &Model<Project>,
7358                path: &ProjectPath,
7359                cx: &mut AppContext,
7360            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7361                if path.path.extension().unwrap() == "png" {
7362                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
7363                } else {
7364                    None
7365                }
7366            }
7367
7368            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7369                None
7370            }
7371
7372            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7373                None
7374            }
7375        }
7376
7377        impl Item for TestPngItemView {
7378            type Event = ();
7379        }
7380        impl EventEmitter<()> for TestPngItemView {}
7381        impl FocusableView for TestPngItemView {
7382            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7383                self.focus_handle.clone()
7384            }
7385        }
7386
7387        impl Render for TestPngItemView {
7388            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7389                Empty
7390            }
7391        }
7392
7393        impl ProjectItem for TestPngItemView {
7394            type Item = TestPngItem;
7395
7396            fn for_project_item(
7397                _project: Model<Project>,
7398                _item: Model<Self::Item>,
7399                cx: &mut ViewContext<Self>,
7400            ) -> Self
7401            where
7402                Self: Sized,
7403            {
7404                Self {
7405                    focus_handle: cx.focus_handle(),
7406                }
7407            }
7408        }
7409
7410        // View
7411        struct TestIpynbItemView {
7412            focus_handle: FocusHandle,
7413        }
7414        // Model
7415        struct TestIpynbItem {}
7416
7417        impl project::Item for TestIpynbItem {
7418            fn try_open(
7419                _project: &Model<Project>,
7420                path: &ProjectPath,
7421                cx: &mut AppContext,
7422            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7423                if path.path.extension().unwrap() == "ipynb" {
7424                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
7425                } else {
7426                    None
7427                }
7428            }
7429
7430            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7431                None
7432            }
7433
7434            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7435                None
7436            }
7437        }
7438
7439        impl Item for TestIpynbItemView {
7440            type Event = ();
7441        }
7442        impl EventEmitter<()> for TestIpynbItemView {}
7443        impl FocusableView for TestIpynbItemView {
7444            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7445                self.focus_handle.clone()
7446            }
7447        }
7448
7449        impl Render for TestIpynbItemView {
7450            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7451                Empty
7452            }
7453        }
7454
7455        impl ProjectItem for TestIpynbItemView {
7456            type Item = TestIpynbItem;
7457
7458            fn for_project_item(
7459                _project: Model<Project>,
7460                _item: Model<Self::Item>,
7461                cx: &mut ViewContext<Self>,
7462            ) -> Self
7463            where
7464                Self: Sized,
7465            {
7466                Self {
7467                    focus_handle: cx.focus_handle(),
7468                }
7469            }
7470        }
7471
7472        struct TestAlternatePngItemView {
7473            focus_handle: FocusHandle,
7474        }
7475
7476        impl Item for TestAlternatePngItemView {
7477            type Event = ();
7478        }
7479
7480        impl EventEmitter<()> for TestAlternatePngItemView {}
7481        impl FocusableView for TestAlternatePngItemView {
7482            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7483                self.focus_handle.clone()
7484            }
7485        }
7486
7487        impl Render for TestAlternatePngItemView {
7488            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7489                Empty
7490            }
7491        }
7492
7493        impl ProjectItem for TestAlternatePngItemView {
7494            type Item = TestPngItem;
7495
7496            fn for_project_item(
7497                _project: Model<Project>,
7498                _item: Model<Self::Item>,
7499                cx: &mut ViewContext<Self>,
7500            ) -> Self
7501            where
7502                Self: Sized,
7503            {
7504                Self {
7505                    focus_handle: cx.focus_handle(),
7506                }
7507            }
7508        }
7509
7510        #[gpui::test]
7511        async fn test_register_project_item(cx: &mut TestAppContext) {
7512            init_test(cx);
7513
7514            cx.update(|cx| {
7515                register_project_item::<TestPngItemView>(cx);
7516                register_project_item::<TestIpynbItemView>(cx);
7517            });
7518
7519            let fs = FakeFs::new(cx.executor());
7520            fs.insert_tree(
7521                "/root1",
7522                json!({
7523                    "one.png": "BINARYDATAHERE",
7524                    "two.ipynb": "{ totally a notebook }",
7525                    "three.txt": "editing text, sure why not?"
7526                }),
7527            )
7528            .await;
7529
7530            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7531            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7532
7533            let worktree_id = project.update(cx, |project, cx| {
7534                project.worktrees(cx).next().unwrap().read(cx).id()
7535            });
7536
7537            let handle = workspace
7538                .update(cx, |workspace, cx| {
7539                    let project_path = (worktree_id, "one.png");
7540                    workspace.open_path(project_path, None, true, cx)
7541                })
7542                .await
7543                .unwrap();
7544
7545            // Now we can check if the handle we got back errored or not
7546            assert_eq!(
7547                handle.to_any().entity_type(),
7548                TypeId::of::<TestPngItemView>()
7549            );
7550
7551            let handle = workspace
7552                .update(cx, |workspace, cx| {
7553                    let project_path = (worktree_id, "two.ipynb");
7554                    workspace.open_path(project_path, None, true, cx)
7555                })
7556                .await
7557                .unwrap();
7558
7559            assert_eq!(
7560                handle.to_any().entity_type(),
7561                TypeId::of::<TestIpynbItemView>()
7562            );
7563
7564            let handle = workspace
7565                .update(cx, |workspace, cx| {
7566                    let project_path = (worktree_id, "three.txt");
7567                    workspace.open_path(project_path, None, true, cx)
7568                })
7569                .await;
7570            assert!(handle.is_err());
7571        }
7572
7573        #[gpui::test]
7574        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
7575            init_test(cx);
7576
7577            cx.update(|cx| {
7578                register_project_item::<TestPngItemView>(cx);
7579                register_project_item::<TestAlternatePngItemView>(cx);
7580            });
7581
7582            let fs = FakeFs::new(cx.executor());
7583            fs.insert_tree(
7584                "/root1",
7585                json!({
7586                    "one.png": "BINARYDATAHERE",
7587                    "two.ipynb": "{ totally a notebook }",
7588                    "three.txt": "editing text, sure why not?"
7589                }),
7590            )
7591            .await;
7592
7593            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7594            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7595
7596            let worktree_id = project.update(cx, |project, cx| {
7597                project.worktrees(cx).next().unwrap().read(cx).id()
7598            });
7599
7600            let handle = workspace
7601                .update(cx, |workspace, cx| {
7602                    let project_path = (worktree_id, "one.png");
7603                    workspace.open_path(project_path, None, true, cx)
7604                })
7605                .await
7606                .unwrap();
7607
7608            // This _must_ be the second item registered
7609            assert_eq!(
7610                handle.to_any().entity_type(),
7611                TypeId::of::<TestAlternatePngItemView>()
7612            );
7613
7614            let handle = workspace
7615                .update(cx, |workspace, cx| {
7616                    let project_path = (worktree_id, "three.txt");
7617                    workspace.open_path(project_path, None, true, cx)
7618                })
7619                .await;
7620            assert!(handle.is_err());
7621        }
7622    }
7623
7624    pub fn init_test(cx: &mut TestAppContext) {
7625        cx.update(|cx| {
7626            let settings_store = SettingsStore::test(cx);
7627            cx.set_global(settings_store);
7628            theme::init(theme::LoadThemes::JustBase, cx);
7629            language::init(cx);
7630            crate::init_settings(cx);
7631            Project::init_settings(cx);
7632        });
7633    }
7634}