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