workspace.rs

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