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<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 keystrokes: Vec<Keystroke> = action
1788            .0
1789            .split(' ')
1790            .flat_map(|k| Keystroke::parse(k).log_err())
1791            .collect();
1792        keystrokes.reverse();
1793
1794        self.dispatching_keystrokes
1795            .borrow_mut()
1796            .append(&mut keystrokes);
1797
1798        let keystrokes = self.dispatching_keystrokes.clone();
1799        cx.window_context()
1800            .spawn(|mut cx| async move {
1801                // limit to 100 keystrokes to avoid infinite recursion.
1802                for _ in 0..100 {
1803                    let Some(keystroke) = keystrokes.borrow_mut().pop() else {
1804                        return Ok(());
1805                    };
1806                    cx.update(|cx| {
1807                        let focused = cx.focused();
1808                        cx.dispatch_keystroke(keystroke.clone());
1809                        if cx.focused() != focused {
1810                            // dispatch_keystroke may cause the focus to change.
1811                            // draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
1812                            // And we need that to happen before the next keystroke to keep vim mode happy...
1813                            // (Note that the tests always do this implicitly, so you must manually test with something like:
1814                            //   "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
1815                            // )
1816                            cx.draw();
1817                        }
1818                    })?;
1819                }
1820                keystrokes.borrow_mut().clear();
1821                Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
1822            })
1823            .detach_and_log_err(cx);
1824    }
1825
1826    fn save_all_internal(
1827        &mut self,
1828        mut save_intent: SaveIntent,
1829        cx: &mut ViewContext<Self>,
1830    ) -> Task<Result<bool>> {
1831        if self.project.read(cx).is_disconnected(cx) {
1832            return Task::ready(Ok(true));
1833        }
1834        let dirty_items = self
1835            .panes
1836            .iter()
1837            .flat_map(|pane| {
1838                pane.read(cx).items().filter_map(|item| {
1839                    if item.is_dirty(cx) {
1840                        item.tab_description(0, cx);
1841                        Some((pane.downgrade(), item.boxed_clone()))
1842                    } else {
1843                        None
1844                    }
1845                })
1846            })
1847            .collect::<Vec<_>>();
1848
1849        let project = self.project.clone();
1850        cx.spawn(|workspace, mut cx| async move {
1851            let dirty_items = if save_intent == SaveIntent::Close && !dirty_items.is_empty() {
1852                let (serialize_tasks, remaining_dirty_items) =
1853                    workspace.update(&mut cx, |workspace, cx| {
1854                        let mut remaining_dirty_items = Vec::new();
1855                        let mut serialize_tasks = Vec::new();
1856                        for (pane, item) in dirty_items {
1857                            if let Some(task) = item
1858                                .to_serializable_item_handle(cx)
1859                                .and_then(|handle| handle.serialize(workspace, true, cx))
1860                            {
1861                                serialize_tasks.push(task);
1862                            } else {
1863                                remaining_dirty_items.push((pane, item));
1864                            }
1865                        }
1866                        (serialize_tasks, remaining_dirty_items)
1867                    })?;
1868
1869                futures::future::try_join_all(serialize_tasks).await?;
1870
1871                if remaining_dirty_items.len() > 1 {
1872                    let answer = workspace.update(&mut cx, |_, cx| {
1873                        let (prompt, detail) = Pane::file_names_for_prompt(
1874                            &mut remaining_dirty_items.iter().map(|(_, handle)| handle),
1875                            remaining_dirty_items.len(),
1876                            cx,
1877                        );
1878                        cx.prompt(
1879                            PromptLevel::Warning,
1880                            &prompt,
1881                            Some(&detail),
1882                            &["Save all", "Discard all", "Cancel"],
1883                        )
1884                    })?;
1885                    match answer.await.log_err() {
1886                        Some(0) => save_intent = SaveIntent::SaveAll,
1887                        Some(1) => save_intent = SaveIntent::Skip,
1888                        _ => {}
1889                    }
1890                }
1891
1892                remaining_dirty_items
1893            } else {
1894                dirty_items
1895            };
1896
1897            for (pane, item) in dirty_items {
1898                let (singleton, project_entry_ids) =
1899                    cx.update(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1900                if singleton || !project_entry_ids.is_empty() {
1901                    if let Some(ix) =
1902                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1903                    {
1904                        if !Pane::save_item(
1905                            project.clone(),
1906                            &pane,
1907                            ix,
1908                            &*item,
1909                            save_intent,
1910                            &mut cx,
1911                        )
1912                        .await?
1913                        {
1914                            return Ok(false);
1915                        }
1916                    }
1917                }
1918            }
1919            Ok(true)
1920        })
1921    }
1922
1923    pub fn open_workspace_for_paths(
1924        &mut self,
1925        replace_current_window: bool,
1926        paths: Vec<PathBuf>,
1927        cx: &mut ViewContext<Self>,
1928    ) -> Task<Result<()>> {
1929        let window = cx.window_handle().downcast::<Self>();
1930        let is_remote = self.project.read(cx).is_via_collab();
1931        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1932        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1933
1934        let window_to_replace = if replace_current_window {
1935            window
1936        } else if is_remote || has_worktree || has_dirty_items {
1937            None
1938        } else {
1939            window
1940        };
1941        let app_state = self.app_state.clone();
1942
1943        cx.spawn(|_, mut cx| async move {
1944            cx.update(|cx| {
1945                open_paths(
1946                    &paths,
1947                    app_state,
1948                    OpenOptions {
1949                        replace_window: window_to_replace,
1950                        ..Default::default()
1951                    },
1952                    cx,
1953                )
1954            })?
1955            .await?;
1956            Ok(())
1957        })
1958    }
1959
1960    #[allow(clippy::type_complexity)]
1961    pub fn open_paths(
1962        &mut self,
1963        mut abs_paths: Vec<PathBuf>,
1964        visible: OpenVisible,
1965        pane: Option<WeakView<Pane>>,
1966        cx: &mut ViewContext<Self>,
1967    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1968        log::info!("open paths {abs_paths:?}");
1969
1970        let fs = self.app_state.fs.clone();
1971
1972        // Sort the paths to ensure we add worktrees for parents before their children.
1973        abs_paths.sort_unstable();
1974        cx.spawn(move |this, mut cx| async move {
1975            let mut tasks = Vec::with_capacity(abs_paths.len());
1976
1977            for abs_path in &abs_paths {
1978                let visible = match visible {
1979                    OpenVisible::All => Some(true),
1980                    OpenVisible::None => Some(false),
1981                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
1982                        Some(Some(metadata)) => Some(!metadata.is_dir),
1983                        Some(None) => Some(true),
1984                        None => None,
1985                    },
1986                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
1987                        Some(Some(metadata)) => Some(metadata.is_dir),
1988                        Some(None) => Some(false),
1989                        None => None,
1990                    },
1991                };
1992                let project_path = match visible {
1993                    Some(visible) => match this
1994                        .update(&mut cx, |this, cx| {
1995                            Workspace::project_path_for_path(
1996                                this.project.clone(),
1997                                abs_path,
1998                                visible,
1999                                cx,
2000                            )
2001                        })
2002                        .log_err()
2003                    {
2004                        Some(project_path) => project_path.await.log_err(),
2005                        None => None,
2006                    },
2007                    None => None,
2008                };
2009
2010                let this = this.clone();
2011                let abs_path = abs_path.clone();
2012                let fs = fs.clone();
2013                let pane = pane.clone();
2014                let task = cx.spawn(move |mut cx| async move {
2015                    let (worktree, project_path) = project_path?;
2016                    if fs.is_dir(&abs_path).await {
2017                        this.update(&mut cx, |workspace, cx| {
2018                            let worktree = worktree.read(cx);
2019                            let worktree_abs_path = worktree.abs_path();
2020                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
2021                                worktree.root_entry()
2022                            } else {
2023                                abs_path
2024                                    .strip_prefix(worktree_abs_path.as_ref())
2025                                    .ok()
2026                                    .and_then(|relative_path| {
2027                                        worktree.entry_for_path(relative_path)
2028                                    })
2029                            }
2030                            .map(|entry| entry.id);
2031                            if let Some(entry_id) = entry_id {
2032                                workspace.project.update(cx, |_, cx| {
2033                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
2034                                })
2035                            }
2036                        })
2037                        .log_err()?;
2038                        None
2039                    } else {
2040                        Some(
2041                            this.update(&mut cx, |this, cx| {
2042                                this.open_path(project_path, pane, true, cx)
2043                            })
2044                            .log_err()?
2045                            .await,
2046                        )
2047                    }
2048                });
2049                tasks.push(task);
2050            }
2051
2052            futures::future::join_all(tasks).await
2053        })
2054    }
2055
2056    pub fn open_resolved_path(
2057        &mut self,
2058        path: ResolvedPath,
2059        cx: &mut ViewContext<Self>,
2060    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2061        match path {
2062            ResolvedPath::ProjectPath { project_path, .. } => {
2063                self.open_path(project_path, None, true, cx)
2064            }
2065            ResolvedPath::AbsPath { path, .. } => self.open_abs_path(path, false, cx),
2066        }
2067    }
2068
2069    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
2070        let project = self.project.read(cx);
2071        if project.is_via_collab() {
2072            self.show_error(
2073                &anyhow!("You cannot add folders to someone else's project"),
2074                cx,
2075            );
2076            return;
2077        }
2078        let paths = self.prompt_for_open_path(
2079            PathPromptOptions {
2080                files: false,
2081                directories: true,
2082                multiple: true,
2083            },
2084            DirectoryLister::Project(self.project.clone()),
2085            cx,
2086        );
2087        cx.spawn(|this, mut cx| async move {
2088            if let Some(paths) = paths.await.log_err().flatten() {
2089                let results = this
2090                    .update(&mut cx, |this, cx| {
2091                        this.open_paths(paths, OpenVisible::All, None, cx)
2092                    })?
2093                    .await;
2094                for result in results.into_iter().flatten() {
2095                    result.log_err();
2096                }
2097            }
2098            anyhow::Ok(())
2099        })
2100        .detach_and_log_err(cx);
2101    }
2102
2103    pub fn project_path_for_path(
2104        project: Model<Project>,
2105        abs_path: &Path,
2106        visible: bool,
2107        cx: &mut AppContext,
2108    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
2109        let entry = project.update(cx, |project, cx| {
2110            project.find_or_create_worktree(abs_path, visible, cx)
2111        });
2112        cx.spawn(|mut cx| async move {
2113            let (worktree, path) = entry.await?;
2114            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
2115            Ok((
2116                worktree,
2117                ProjectPath {
2118                    worktree_id,
2119                    path: path.into(),
2120                },
2121            ))
2122        })
2123    }
2124
2125    pub fn items<'a>(
2126        &'a self,
2127        cx: &'a AppContext,
2128    ) -> impl 'a + Iterator<Item = &'a Box<dyn ItemHandle>> {
2129        self.panes.iter().flat_map(|pane| pane.read(cx).items())
2130    }
2131
2132    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
2133        self.items_of_type(cx).max_by_key(|item| item.item_id())
2134    }
2135
2136    pub fn items_of_type<'a, T: Item>(
2137        &'a self,
2138        cx: &'a AppContext,
2139    ) -> impl 'a + Iterator<Item = View<T>> {
2140        self.panes
2141            .iter()
2142            .flat_map(|pane| pane.read(cx).items_of_type())
2143    }
2144
2145    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
2146        self.active_pane().read(cx).active_item()
2147    }
2148
2149    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
2150        let item = self.active_item(cx)?;
2151        item.to_any().downcast::<I>().ok()
2152    }
2153
2154    fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
2155        self.active_item(cx).and_then(|item| item.project_path(cx))
2156    }
2157
2158    pub fn save_active_item(
2159        &mut self,
2160        save_intent: SaveIntent,
2161        cx: &mut WindowContext,
2162    ) -> Task<Result<()>> {
2163        let project = self.project.clone();
2164        let pane = self.active_pane();
2165        let item_ix = pane.read(cx).active_item_index();
2166        let item = pane.read(cx).active_item();
2167        let pane = pane.downgrade();
2168
2169        cx.spawn(|mut cx| async move {
2170            if let Some(item) = item {
2171                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
2172                    .await
2173                    .map(|_| ())
2174            } else {
2175                Ok(())
2176            }
2177        })
2178    }
2179
2180    pub fn close_inactive_items_and_panes(
2181        &mut self,
2182        action: &CloseInactiveTabsAndPanes,
2183        cx: &mut ViewContext<Self>,
2184    ) {
2185        if let Some(task) =
2186            self.close_all_internal(true, action.save_intent.unwrap_or(SaveIntent::Close), cx)
2187        {
2188            task.detach_and_log_err(cx)
2189        }
2190    }
2191
2192    pub fn close_all_items_and_panes(
2193        &mut self,
2194        action: &CloseAllItemsAndPanes,
2195        cx: &mut ViewContext<Self>,
2196    ) {
2197        if let Some(task) =
2198            self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
2199        {
2200            task.detach_and_log_err(cx)
2201        }
2202    }
2203
2204    fn close_all_internal(
2205        &mut self,
2206        retain_active_pane: bool,
2207        save_intent: SaveIntent,
2208        cx: &mut ViewContext<Self>,
2209    ) -> Option<Task<Result<()>>> {
2210        let current_pane = self.active_pane();
2211
2212        let mut tasks = Vec::new();
2213
2214        if retain_active_pane {
2215            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
2216                pane.close_inactive_items(&CloseInactiveItems { save_intent: None }, cx)
2217            }) {
2218                tasks.push(current_pane_close);
2219            };
2220        }
2221
2222        for pane in self.panes() {
2223            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
2224                continue;
2225            }
2226
2227            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
2228                pane.close_all_items(
2229                    &CloseAllItems {
2230                        save_intent: Some(save_intent),
2231                    },
2232                    cx,
2233                )
2234            }) {
2235                tasks.push(close_pane_items)
2236            }
2237        }
2238
2239        if tasks.is_empty() {
2240            None
2241        } else {
2242            Some(cx.spawn(|_, _| async move {
2243                for task in tasks {
2244                    task.await?
2245                }
2246                Ok(())
2247            }))
2248        }
2249    }
2250
2251    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
2252        let dock = match dock_side {
2253            DockPosition::Left => &self.left_dock,
2254            DockPosition::Bottom => &self.bottom_dock,
2255            DockPosition::Right => &self.right_dock,
2256        };
2257        let mut focus_center = false;
2258        let mut reveal_dock = false;
2259        dock.update(cx, |dock, cx| {
2260            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
2261            let was_visible = dock.is_open() && !other_is_zoomed;
2262            dock.set_open(!was_visible, cx);
2263
2264            if let Some(active_panel) = dock.active_panel() {
2265                if was_visible {
2266                    if active_panel.focus_handle(cx).contains_focused(cx) {
2267                        focus_center = true;
2268                    }
2269                } else {
2270                    let focus_handle = &active_panel.focus_handle(cx);
2271                    cx.focus(focus_handle);
2272                    reveal_dock = true;
2273                }
2274            }
2275        });
2276
2277        if reveal_dock {
2278            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
2279        }
2280
2281        if focus_center {
2282            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
2283        }
2284
2285        cx.notify();
2286        self.serialize_workspace(cx);
2287    }
2288
2289    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
2290        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
2291
2292        for dock in docks {
2293            dock.update(cx, |dock, cx| {
2294                dock.set_open(false, cx);
2295            });
2296        }
2297
2298        cx.focus_self();
2299        cx.notify();
2300        self.serialize_workspace(cx);
2301    }
2302
2303    /// Transfer focus to the panel of the given type.
2304    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
2305        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
2306        panel.to_any().downcast().ok()
2307    }
2308
2309    /// Focus the panel of the given type if it isn't already focused. If it is
2310    /// already focused, then transfer focus back to the workspace center.
2311    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
2312        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
2313            !panel.focus_handle(cx).contains_focused(cx)
2314        });
2315    }
2316
2317    pub fn activate_panel_for_proto_id(
2318        &mut self,
2319        panel_id: PanelId,
2320        cx: &mut ViewContext<Self>,
2321    ) -> Option<Arc<dyn PanelHandle>> {
2322        let mut panel = None;
2323        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
2324            if let Some(panel_index) = dock.read(cx).panel_index_for_proto_id(panel_id) {
2325                panel = dock.update(cx, |dock, cx| {
2326                    dock.activate_panel(panel_index, cx);
2327                    dock.set_open(true, cx);
2328                    dock.active_panel().cloned()
2329                });
2330                break;
2331            }
2332        }
2333
2334        if panel.is_some() {
2335            cx.notify();
2336            self.serialize_workspace(cx);
2337        }
2338
2339        panel
2340    }
2341
2342    /// Focus or unfocus the given panel type, depending on the given callback.
2343    fn focus_or_unfocus_panel<T: Panel>(
2344        &mut self,
2345        cx: &mut ViewContext<Self>,
2346        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
2347    ) -> Option<Arc<dyn PanelHandle>> {
2348        let mut result_panel = None;
2349        let mut serialize = false;
2350        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
2351            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2352                let mut focus_center = false;
2353                let panel = dock.update(cx, |dock, cx| {
2354                    dock.activate_panel(panel_index, cx);
2355
2356                    let panel = dock.active_panel().cloned();
2357                    if let Some(panel) = panel.as_ref() {
2358                        if should_focus(&**panel, cx) {
2359                            dock.set_open(true, cx);
2360                            panel.focus_handle(cx).focus(cx);
2361                        } else {
2362                            focus_center = true;
2363                        }
2364                    }
2365                    panel
2366                });
2367
2368                if focus_center {
2369                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
2370                }
2371
2372                result_panel = panel;
2373                serialize = true;
2374                break;
2375            }
2376        }
2377
2378        if serialize {
2379            self.serialize_workspace(cx);
2380        }
2381
2382        cx.notify();
2383        result_panel
2384    }
2385
2386    /// Open the panel of the given type
2387    pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
2388        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
2389            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2390                dock.update(cx, |dock, cx| {
2391                    dock.activate_panel(panel_index, cx);
2392                    dock.set_open(true, cx);
2393                });
2394            }
2395        }
2396    }
2397
2398    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
2399        [&self.left_dock, &self.bottom_dock, &self.right_dock]
2400            .iter()
2401            .find_map(|dock| dock.read(cx).panel::<T>())
2402    }
2403
2404    fn dismiss_zoomed_items_to_reveal(
2405        &mut self,
2406        dock_to_reveal: Option<DockPosition>,
2407        cx: &mut ViewContext<Self>,
2408    ) {
2409        // If a center pane is zoomed, unzoom it.
2410        for pane in &self.panes {
2411            if pane != &self.active_pane || dock_to_reveal.is_some() {
2412                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2413            }
2414        }
2415
2416        // If another dock is zoomed, hide it.
2417        let mut focus_center = false;
2418        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
2419            dock.update(cx, |dock, cx| {
2420                if Some(dock.position()) != dock_to_reveal {
2421                    if let Some(panel) = dock.active_panel() {
2422                        if panel.is_zoomed(cx) {
2423                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
2424                            dock.set_open(false, cx);
2425                        }
2426                    }
2427                }
2428            });
2429        }
2430
2431        if focus_center {
2432            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
2433        }
2434
2435        if self.zoomed_position != dock_to_reveal {
2436            self.zoomed = None;
2437            self.zoomed_position = None;
2438            cx.emit(Event::ZoomChanged);
2439        }
2440
2441        cx.notify();
2442    }
2443
2444    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
2445        let pane = cx.new_view(|cx| {
2446            Pane::new(
2447                self.weak_handle(),
2448                self.project.clone(),
2449                self.pane_history_timestamp.clone(),
2450                None,
2451                NewFile.boxed_clone(),
2452                cx,
2453            )
2454        });
2455        cx.subscribe(&pane, Self::handle_pane_event).detach();
2456        self.panes.push(pane.clone());
2457        cx.focus_view(&pane);
2458        cx.emit(Event::PaneAdded(pane.clone()));
2459        pane
2460    }
2461
2462    pub fn add_item_to_center(
2463        &mut self,
2464        item: Box<dyn ItemHandle>,
2465        cx: &mut ViewContext<Self>,
2466    ) -> bool {
2467        if let Some(center_pane) = self.last_active_center_pane.clone() {
2468            if let Some(center_pane) = center_pane.upgrade() {
2469                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
2470                true
2471            } else {
2472                false
2473            }
2474        } else {
2475            false
2476        }
2477    }
2478
2479    pub fn add_item_to_active_pane(
2480        &mut self,
2481        item: Box<dyn ItemHandle>,
2482        destination_index: Option<usize>,
2483        focus_item: bool,
2484        cx: &mut WindowContext,
2485    ) {
2486        self.add_item(
2487            self.active_pane.clone(),
2488            item,
2489            destination_index,
2490            false,
2491            focus_item,
2492            cx,
2493        )
2494    }
2495
2496    pub fn add_item(
2497        &mut self,
2498        pane: View<Pane>,
2499        item: Box<dyn ItemHandle>,
2500        destination_index: Option<usize>,
2501        activate_pane: bool,
2502        focus_item: bool,
2503        cx: &mut WindowContext,
2504    ) {
2505        if let Some(text) = item.telemetry_event_text(cx) {
2506            self.client()
2507                .telemetry()
2508                .report_app_event(format!("{}: open", text));
2509        }
2510
2511        pane.update(cx, |pane, cx| {
2512            pane.add_item(item, activate_pane, focus_item, destination_index, cx)
2513        });
2514    }
2515
2516    pub fn split_item(
2517        &mut self,
2518        split_direction: SplitDirection,
2519        item: Box<dyn ItemHandle>,
2520        cx: &mut ViewContext<Self>,
2521    ) {
2522        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
2523        self.add_item(new_pane, item, None, true, true, cx);
2524    }
2525
2526    pub fn open_abs_path(
2527        &mut self,
2528        abs_path: PathBuf,
2529        visible: bool,
2530        cx: &mut ViewContext<Self>,
2531    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2532        cx.spawn(|workspace, mut cx| async move {
2533            let open_paths_task_result = workspace
2534                .update(&mut cx, |workspace, cx| {
2535                    workspace.open_paths(
2536                        vec![abs_path.clone()],
2537                        if visible {
2538                            OpenVisible::All
2539                        } else {
2540                            OpenVisible::None
2541                        },
2542                        None,
2543                        cx,
2544                    )
2545                })
2546                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
2547                .await;
2548            anyhow::ensure!(
2549                open_paths_task_result.len() == 1,
2550                "open abs path {abs_path:?} task returned incorrect number of results"
2551            );
2552            match open_paths_task_result
2553                .into_iter()
2554                .next()
2555                .expect("ensured single task result")
2556            {
2557                Some(open_result) => {
2558                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
2559                }
2560                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
2561            }
2562        })
2563    }
2564
2565    pub fn split_abs_path(
2566        &mut self,
2567        abs_path: PathBuf,
2568        visible: bool,
2569        cx: &mut ViewContext<Self>,
2570    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2571        let project_path_task =
2572            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2573        cx.spawn(|this, mut cx| async move {
2574            let (_, path) = project_path_task.await?;
2575            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2576                .await
2577        })
2578    }
2579
2580    pub fn open_path(
2581        &mut self,
2582        path: impl Into<ProjectPath>,
2583        pane: Option<WeakView<Pane>>,
2584        focus_item: bool,
2585        cx: &mut WindowContext,
2586    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2587        self.open_path_preview(path, pane, focus_item, false, cx)
2588    }
2589
2590    pub fn open_path_preview(
2591        &mut self,
2592        path: impl Into<ProjectPath>,
2593        pane: Option<WeakView<Pane>>,
2594        focus_item: bool,
2595        allow_preview: bool,
2596        cx: &mut WindowContext,
2597    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2598        let pane = pane.unwrap_or_else(|| {
2599            self.last_active_center_pane.clone().unwrap_or_else(|| {
2600                self.panes
2601                    .first()
2602                    .expect("There must be an active pane")
2603                    .downgrade()
2604            })
2605        });
2606
2607        let task = self.load_path(path.into(), cx);
2608        cx.spawn(move |mut cx| async move {
2609            let (project_entry_id, build_item) = task.await?;
2610            pane.update(&mut cx, |pane, cx| {
2611                pane.open_item(project_entry_id, focus_item, allow_preview, cx, build_item)
2612            })
2613        })
2614    }
2615
2616    pub fn split_path(
2617        &mut self,
2618        path: impl Into<ProjectPath>,
2619        cx: &mut ViewContext<Self>,
2620    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2621        self.split_path_preview(path, false, cx)
2622    }
2623
2624    pub fn split_path_preview(
2625        &mut self,
2626        path: impl Into<ProjectPath>,
2627        allow_preview: bool,
2628        cx: &mut ViewContext<Self>,
2629    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2630        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2631            self.panes
2632                .first()
2633                .expect("There must be an active pane")
2634                .downgrade()
2635        });
2636
2637        if let Member::Pane(center_pane) = &self.center.root {
2638            if center_pane.read(cx).items_len() == 0 {
2639                return self.open_path(path, Some(pane), true, cx);
2640            }
2641        }
2642
2643        let task = self.load_path(path.into(), cx);
2644        cx.spawn(|this, mut cx| async move {
2645            let (project_entry_id, build_item) = task.await?;
2646            this.update(&mut cx, move |this, cx| -> Option<_> {
2647                let pane = pane.upgrade()?;
2648                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2649                new_pane.update(cx, |new_pane, cx| {
2650                    Some(new_pane.open_item(project_entry_id, true, allow_preview, cx, build_item))
2651                })
2652            })
2653            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2654        })
2655    }
2656
2657    fn load_path(
2658        &mut self,
2659        path: ProjectPath,
2660        cx: &mut WindowContext,
2661    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
2662        let project = self.project().clone();
2663        let project_item_builders = cx.default_global::<ProjectItemOpeners>().clone();
2664        let Some(open_project_item) = project_item_builders
2665            .iter()
2666            .rev()
2667            .find_map(|open_project_item| open_project_item(&project, &path, cx))
2668        else {
2669            return Task::ready(Err(anyhow!("cannot open file {:?}", path.path)));
2670        };
2671        open_project_item
2672    }
2673
2674    pub fn find_project_item<T>(
2675        &self,
2676        pane: &View<Pane>,
2677        project_item: &Model<T::Item>,
2678        cx: &AppContext,
2679    ) -> Option<View<T>>
2680    where
2681        T: ProjectItem,
2682    {
2683        use project::Item as _;
2684        let project_item = project_item.read(cx);
2685        let entry_id = project_item.entry_id(cx);
2686        let project_path = project_item.project_path(cx);
2687
2688        let mut item = None;
2689        if let Some(entry_id) = entry_id {
2690            item = pane.read(cx).item_for_entry(entry_id, cx);
2691        }
2692        if item.is_none() {
2693            if let Some(project_path) = project_path {
2694                item = pane.read(cx).item_for_path(project_path, cx);
2695            }
2696        }
2697
2698        item.and_then(|item| item.downcast::<T>())
2699    }
2700
2701    pub fn is_project_item_open<T>(
2702        &self,
2703        pane: &View<Pane>,
2704        project_item: &Model<T::Item>,
2705        cx: &AppContext,
2706    ) -> bool
2707    where
2708        T: ProjectItem,
2709    {
2710        self.find_project_item::<T>(pane, project_item, cx)
2711            .is_some()
2712    }
2713
2714    pub fn open_project_item<T>(
2715        &mut self,
2716        pane: View<Pane>,
2717        project_item: Model<T::Item>,
2718        activate_pane: bool,
2719        focus_item: bool,
2720        cx: &mut ViewContext<Self>,
2721    ) -> View<T>
2722    where
2723        T: ProjectItem,
2724    {
2725        if let Some(item) = self.find_project_item(&pane, &project_item, cx) {
2726            self.activate_item(&item, activate_pane, focus_item, cx);
2727            return item;
2728        }
2729
2730        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2731        let item_id = item.item_id();
2732        let mut destination_index = None;
2733        pane.update(cx, |pane, cx| {
2734            if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
2735                if let Some(preview_item_id) = pane.preview_item_id() {
2736                    if preview_item_id != item_id {
2737                        destination_index = pane.close_current_preview_item(cx);
2738                    }
2739                }
2740            }
2741            pane.set_preview_item_id(Some(item.item_id()), cx)
2742        });
2743
2744        self.add_item(
2745            pane,
2746            Box::new(item.clone()),
2747            destination_index,
2748            activate_pane,
2749            focus_item,
2750            cx,
2751        );
2752        item
2753    }
2754
2755    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2756        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2757            self.active_pane.update(cx, |pane, cx| {
2758                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2759            });
2760        }
2761    }
2762
2763    pub fn activate_item(
2764        &mut self,
2765        item: &dyn ItemHandle,
2766        activate_pane: bool,
2767        focus_item: bool,
2768        cx: &mut WindowContext,
2769    ) -> bool {
2770        let result = self.panes.iter().find_map(|pane| {
2771            pane.read(cx)
2772                .index_for_item(item)
2773                .map(|ix| (pane.clone(), ix))
2774        });
2775        if let Some((pane, ix)) = result {
2776            pane.update(cx, |pane, cx| {
2777                pane.activate_item(ix, activate_pane, focus_item, cx)
2778            });
2779            true
2780        } else {
2781            false
2782        }
2783    }
2784
2785    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2786        let panes = self.center.panes();
2787        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2788            cx.focus_view(&pane);
2789        } else {
2790            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2791        }
2792    }
2793
2794    pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
2795        let panes = self.center.panes();
2796        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2797            let next_ix = (ix + 1) % panes.len();
2798            let next_pane = panes[next_ix].clone();
2799            cx.focus_view(&next_pane);
2800        }
2801    }
2802
2803    pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
2804        let panes = self.center.panes();
2805        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2806            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2807            let prev_pane = panes[prev_ix].clone();
2808            cx.focus_view(&prev_pane);
2809        }
2810    }
2811
2812    pub fn activate_pane_in_direction(
2813        &mut self,
2814        direction: SplitDirection,
2815        cx: &mut WindowContext,
2816    ) {
2817        use ActivateInDirectionTarget as Target;
2818        enum Origin {
2819            LeftDock,
2820            RightDock,
2821            BottomDock,
2822            Center,
2823        }
2824
2825        let origin: Origin = [
2826            (&self.left_dock, Origin::LeftDock),
2827            (&self.right_dock, Origin::RightDock),
2828            (&self.bottom_dock, Origin::BottomDock),
2829        ]
2830        .into_iter()
2831        .find_map(|(dock, origin)| {
2832            if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() {
2833                Some(origin)
2834            } else {
2835                None
2836            }
2837        })
2838        .unwrap_or(Origin::Center);
2839
2840        let get_last_active_pane = || {
2841            let pane = self
2842                .last_active_center_pane
2843                .clone()
2844                .unwrap_or_else(|| {
2845                    self.panes
2846                        .first()
2847                        .expect("There must be an active pane")
2848                        .downgrade()
2849                })
2850                .upgrade()?;
2851            (pane.read(cx).items_len() != 0).then_some(pane)
2852        };
2853
2854        let try_dock =
2855            |dock: &View<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
2856
2857        let target = match (origin, direction) {
2858            // We're in the center, so we first try to go to a different pane,
2859            // otherwise try to go to a dock.
2860            (Origin::Center, direction) => {
2861                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2862                    Some(Target::Pane(pane))
2863                } else {
2864                    match direction {
2865                        SplitDirection::Up => None,
2866                        SplitDirection::Down => try_dock(&self.bottom_dock),
2867                        SplitDirection::Left => try_dock(&self.left_dock),
2868                        SplitDirection::Right => try_dock(&self.right_dock),
2869                    }
2870                }
2871            }
2872
2873            (Origin::LeftDock, SplitDirection::Right) => {
2874                if let Some(last_active_pane) = get_last_active_pane() {
2875                    Some(Target::Pane(last_active_pane))
2876                } else {
2877                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
2878                }
2879            }
2880
2881            (Origin::LeftDock, SplitDirection::Down)
2882            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
2883
2884            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
2885            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
2886            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
2887
2888            (Origin::RightDock, SplitDirection::Left) => {
2889                if let Some(last_active_pane) = get_last_active_pane() {
2890                    Some(Target::Pane(last_active_pane))
2891                } else {
2892                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
2893                }
2894            }
2895
2896            _ => None,
2897        };
2898
2899        match target {
2900            Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane),
2901            Some(ActivateInDirectionTarget::Dock(dock)) => {
2902                if let Some(panel) = dock.read(cx).active_panel() {
2903                    panel.focus_handle(cx).focus(cx);
2904                } else {
2905                    log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.read(cx).position());
2906                }
2907            }
2908            None => {}
2909        }
2910    }
2911
2912    pub fn find_pane_in_direction(
2913        &mut self,
2914        direction: SplitDirection,
2915        cx: &WindowContext,
2916    ) -> Option<View<Pane>> {
2917        let bounding_box = self.center.bounding_box_for_pane(&self.active_pane)?;
2918        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2919        let center = match cursor {
2920            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2921            _ => bounding_box.center(),
2922        };
2923
2924        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2925
2926        let target = match direction {
2927            SplitDirection::Left => {
2928                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2929            }
2930            SplitDirection::Right => {
2931                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2932            }
2933            SplitDirection::Up => {
2934                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2935            }
2936            SplitDirection::Down => {
2937                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2938            }
2939        };
2940        self.center.pane_at_pixel_position(target).cloned()
2941    }
2942
2943    pub fn swap_pane_in_direction(
2944        &mut self,
2945        direction: SplitDirection,
2946        cx: &mut ViewContext<Self>,
2947    ) {
2948        if let Some(to) = self.find_pane_in_direction(direction, cx) {
2949            self.center.swap(&self.active_pane.clone(), &to);
2950            cx.notify();
2951        }
2952    }
2953
2954    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2955        // This is explicitly hoisted out of the following check for pane identity as
2956        // terminal panel panes are not registered as a center panes.
2957        self.status_bar.update(cx, |status_bar, cx| {
2958            status_bar.set_active_pane(&pane, cx);
2959        });
2960        if self.active_pane != pane {
2961            self.set_active_pane(&pane, cx);
2962        }
2963
2964        if self.last_active_center_pane.is_none() {
2965            self.last_active_center_pane = Some(pane.downgrade());
2966        }
2967
2968        self.dismiss_zoomed_items_to_reveal(None, cx);
2969        if pane.read(cx).is_zoomed() {
2970            self.zoomed = Some(pane.downgrade().into());
2971        } else {
2972            self.zoomed = None;
2973        }
2974        self.zoomed_position = None;
2975        cx.emit(Event::ZoomChanged);
2976        self.update_active_view_for_followers(cx);
2977        pane.model.update(cx, |pane, _| {
2978            pane.track_alternate_file_items();
2979        });
2980
2981        cx.notify();
2982    }
2983
2984    fn set_active_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) {
2985        self.active_pane = pane.clone();
2986        self.active_item_path_changed(cx);
2987        self.last_active_center_pane = Some(pane.downgrade());
2988    }
2989
2990    fn handle_panel_focused(&mut self, cx: &mut ViewContext<Self>) {
2991        self.update_active_view_for_followers(cx);
2992    }
2993
2994    fn handle_pane_event(
2995        &mut self,
2996        pane: View<Pane>,
2997        event: &pane::Event,
2998        cx: &mut ViewContext<Self>,
2999    ) {
3000        match event {
3001            pane::Event::AddItem { item } => {
3002                item.added_to_pane(self, pane, cx);
3003                cx.emit(Event::ItemAdded {
3004                    item: item.boxed_clone(),
3005                });
3006            }
3007            pane::Event::Split(direction) => {
3008                self.split_and_clone(pane, *direction, cx);
3009            }
3010            pane::Event::JoinIntoNext => self.join_pane_into_next(pane, cx),
3011            pane::Event::JoinAll => self.join_all_panes(cx),
3012            pane::Event::Remove { focus_on_pane } => {
3013                self.remove_pane(pane, focus_on_pane.clone(), cx)
3014            }
3015            pane::Event::ActivateItem { local } => {
3016                cx.on_next_frame(|_, cx| {
3017                    cx.invalidate_character_coordinates();
3018                });
3019
3020                pane.model.update(cx, |pane, _| {
3021                    pane.track_alternate_file_items();
3022                });
3023                if *local {
3024                    self.unfollow_in_pane(&pane, cx);
3025                }
3026                if &pane == self.active_pane() {
3027                    self.active_item_path_changed(cx);
3028                    self.update_active_view_for_followers(cx);
3029                }
3030            }
3031            pane::Event::UserSavedItem { item, save_intent } => cx.emit(Event::UserSavedItem {
3032                pane: pane.downgrade(),
3033                item: item.boxed_clone(),
3034                save_intent: *save_intent,
3035            }),
3036            pane::Event::ChangeItemTitle => {
3037                if pane == self.active_pane {
3038                    self.active_item_path_changed(cx);
3039                }
3040                self.update_window_edited(cx);
3041            }
3042            pane::Event::RemoveItem { .. } => {}
3043            pane::Event::RemovedItem { item_id } => {
3044                cx.emit(Event::ActiveItemChanged);
3045                self.update_window_edited(cx);
3046                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
3047                    if entry.get().entity_id() == pane.entity_id() {
3048                        entry.remove();
3049                    }
3050                }
3051            }
3052            pane::Event::Focus => {
3053                cx.on_next_frame(|_, cx| {
3054                    cx.invalidate_character_coordinates();
3055                });
3056                self.handle_pane_focused(pane.clone(), cx);
3057            }
3058            pane::Event::ZoomIn => {
3059                if pane == self.active_pane {
3060                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
3061                    if pane.read(cx).has_focus(cx) {
3062                        self.zoomed = Some(pane.downgrade().into());
3063                        self.zoomed_position = None;
3064                        cx.emit(Event::ZoomChanged);
3065                    }
3066                    cx.notify();
3067                }
3068            }
3069            pane::Event::ZoomOut => {
3070                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
3071                if self.zoomed_position.is_none() {
3072                    self.zoomed = None;
3073                    cx.emit(Event::ZoomChanged);
3074                }
3075                cx.notify();
3076            }
3077        }
3078
3079        self.serialize_workspace(cx);
3080    }
3081
3082    pub fn unfollow_in_pane(
3083        &mut self,
3084        pane: &View<Pane>,
3085        cx: &mut ViewContext<Workspace>,
3086    ) -> Option<PeerId> {
3087        let leader_id = self.leader_for_pane(pane)?;
3088        self.unfollow(leader_id, cx);
3089        Some(leader_id)
3090    }
3091
3092    pub fn split_pane(
3093        &mut self,
3094        pane_to_split: View<Pane>,
3095        split_direction: SplitDirection,
3096        cx: &mut ViewContext<Self>,
3097    ) -> View<Pane> {
3098        let new_pane = self.add_pane(cx);
3099        self.center
3100            .split(&pane_to_split, &new_pane, split_direction)
3101            .unwrap();
3102        cx.notify();
3103        new_pane
3104    }
3105
3106    pub fn split_and_clone(
3107        &mut self,
3108        pane: View<Pane>,
3109        direction: SplitDirection,
3110        cx: &mut ViewContext<Self>,
3111    ) -> Option<View<Pane>> {
3112        let item = pane.read(cx).active_item()?;
3113        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
3114            let new_pane = self.add_pane(cx);
3115            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
3116            self.center.split(&pane, &new_pane, direction).unwrap();
3117            Some(new_pane)
3118        } else {
3119            None
3120        };
3121        cx.notify();
3122        maybe_pane_handle
3123    }
3124
3125    pub fn split_pane_with_item(
3126        &mut self,
3127        pane_to_split: WeakView<Pane>,
3128        split_direction: SplitDirection,
3129        from: WeakView<Pane>,
3130        item_id_to_move: EntityId,
3131        cx: &mut ViewContext<Self>,
3132    ) {
3133        let Some(pane_to_split) = pane_to_split.upgrade() else {
3134            return;
3135        };
3136        let Some(from) = from.upgrade() else {
3137            return;
3138        };
3139
3140        let new_pane = self.add_pane(cx);
3141        move_item(&from, &new_pane, item_id_to_move, 0, cx);
3142        self.center
3143            .split(&pane_to_split, &new_pane, split_direction)
3144            .unwrap();
3145        cx.notify();
3146    }
3147
3148    pub fn split_pane_with_project_entry(
3149        &mut self,
3150        pane_to_split: WeakView<Pane>,
3151        split_direction: SplitDirection,
3152        project_entry: ProjectEntryId,
3153        cx: &mut ViewContext<Self>,
3154    ) -> Option<Task<Result<()>>> {
3155        let pane_to_split = pane_to_split.upgrade()?;
3156        let new_pane = self.add_pane(cx);
3157        self.center
3158            .split(&pane_to_split, &new_pane, split_direction)
3159            .unwrap();
3160
3161        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
3162        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
3163        Some(cx.foreground_executor().spawn(async move {
3164            task.await?;
3165            Ok(())
3166        }))
3167    }
3168
3169    pub fn join_all_panes(&mut self, cx: &mut ViewContext<Self>) {
3170        let active_item = self.active_pane.read(cx).active_item();
3171        for pane in &self.panes {
3172            join_pane_into_active(&self.active_pane, pane, cx);
3173        }
3174        if let Some(active_item) = active_item {
3175            self.activate_item(active_item.as_ref(), true, true, cx);
3176        }
3177        cx.notify();
3178    }
3179
3180    pub fn join_pane_into_next(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
3181        let next_pane = self
3182            .find_pane_in_direction(SplitDirection::Right, cx)
3183            .or_else(|| self.find_pane_in_direction(SplitDirection::Down, cx))
3184            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3185            .or_else(|| self.find_pane_in_direction(SplitDirection::Up, cx));
3186        let Some(next_pane) = next_pane else {
3187            return;
3188        };
3189        move_all_items(&pane, &next_pane, cx);
3190        cx.notify();
3191    }
3192
3193    fn remove_pane(
3194        &mut self,
3195        pane: View<Pane>,
3196        focus_on: Option<View<Pane>>,
3197        cx: &mut ViewContext<Self>,
3198    ) {
3199        if self.center.remove(&pane).unwrap() {
3200            self.force_remove_pane(&pane, &focus_on, cx);
3201            self.unfollow_in_pane(&pane, cx);
3202            self.last_leaders_by_pane.remove(&pane.downgrade());
3203            for removed_item in pane.read(cx).items() {
3204                self.panes_by_item.remove(&removed_item.item_id());
3205            }
3206
3207            cx.notify();
3208        } else {
3209            self.active_item_path_changed(cx);
3210        }
3211        cx.emit(Event::PaneRemoved);
3212    }
3213
3214    pub fn panes(&self) -> &[View<Pane>] {
3215        &self.panes
3216    }
3217
3218    pub fn active_pane(&self) -> &View<Pane> {
3219        &self.active_pane
3220    }
3221
3222    pub fn focused_pane(&self, cx: &WindowContext) -> View<Pane> {
3223        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
3224            if dock.focus_handle(cx).contains_focused(cx) {
3225                if let Some(pane) = dock
3226                    .read(cx)
3227                    .active_panel()
3228                    .and_then(|panel| panel.pane(cx))
3229                {
3230                    return pane;
3231                }
3232            }
3233        }
3234        self.active_pane().clone()
3235    }
3236
3237    pub fn adjacent_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
3238        self.find_pane_in_direction(SplitDirection::Right, cx)
3239            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3240            .unwrap_or_else(|| self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx))
3241            .clone()
3242    }
3243
3244    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
3245        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
3246        weak_pane.upgrade()
3247    }
3248
3249    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
3250        self.follower_states.retain(|leader_id, state| {
3251            if *leader_id == peer_id {
3252                for item in state.items_by_leader_view_id.values() {
3253                    item.view.set_leader_peer_id(None, cx);
3254                }
3255                false
3256            } else {
3257                true
3258            }
3259        });
3260        cx.notify();
3261    }
3262
3263    pub fn start_following(
3264        &mut self,
3265        leader_id: PeerId,
3266        cx: &mut ViewContext<Self>,
3267    ) -> Option<Task<Result<()>>> {
3268        let pane = self.active_pane().clone();
3269
3270        self.last_leaders_by_pane
3271            .insert(pane.downgrade(), leader_id);
3272        self.unfollow(leader_id, cx);
3273        self.unfollow_in_pane(&pane, cx);
3274        self.follower_states.insert(
3275            leader_id,
3276            FollowerState {
3277                center_pane: pane.clone(),
3278                dock_pane: None,
3279                active_view_id: None,
3280                items_by_leader_view_id: Default::default(),
3281            },
3282        );
3283        cx.notify();
3284
3285        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
3286        let project_id = self.project.read(cx).remote_id();
3287        let request = self.app_state.client.request(proto::Follow {
3288            room_id,
3289            project_id,
3290            leader_id: Some(leader_id),
3291        });
3292
3293        Some(cx.spawn(|this, mut cx| async move {
3294            let response = request.await?;
3295            this.update(&mut cx, |this, _| {
3296                let state = this
3297                    .follower_states
3298                    .get_mut(&leader_id)
3299                    .ok_or_else(|| anyhow!("following interrupted"))?;
3300                state.active_view_id = response
3301                    .active_view
3302                    .as_ref()
3303                    .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
3304                Ok::<_, anyhow::Error>(())
3305            })??;
3306            if let Some(view) = response.active_view {
3307                Self::add_view_from_leader(this.clone(), leader_id, &view, &mut cx).await?;
3308            }
3309            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
3310            Ok(())
3311        }))
3312    }
3313
3314    pub fn follow_next_collaborator(
3315        &mut self,
3316        _: &FollowNextCollaborator,
3317        cx: &mut ViewContext<Self>,
3318    ) {
3319        let collaborators = self.project.read(cx).collaborators();
3320        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
3321            let mut collaborators = collaborators.keys().copied();
3322            for peer_id in collaborators.by_ref() {
3323                if peer_id == leader_id {
3324                    break;
3325                }
3326            }
3327            collaborators.next()
3328        } else if let Some(last_leader_id) =
3329            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
3330        {
3331            if collaborators.contains_key(last_leader_id) {
3332                Some(*last_leader_id)
3333            } else {
3334                None
3335            }
3336        } else {
3337            None
3338        };
3339
3340        let pane = self.active_pane.clone();
3341        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
3342        else {
3343            return;
3344        };
3345        if self.unfollow_in_pane(&pane, cx) == Some(leader_id) {
3346            return;
3347        }
3348        if let Some(task) = self.start_following(leader_id, cx) {
3349            task.detach_and_log_err(cx)
3350        }
3351    }
3352
3353    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
3354        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
3355            return;
3356        };
3357        let room = room.read(cx);
3358        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
3359            return;
3360        };
3361
3362        let project = self.project.read(cx);
3363
3364        let other_project_id = match remote_participant.location {
3365            call::ParticipantLocation::External => None,
3366            call::ParticipantLocation::UnsharedProject => None,
3367            call::ParticipantLocation::SharedProject { project_id } => {
3368                if Some(project_id) == project.remote_id() {
3369                    None
3370                } else {
3371                    Some(project_id)
3372                }
3373            }
3374        };
3375
3376        // if they are active in another project, follow there.
3377        if let Some(project_id) = other_project_id {
3378            let app_state = self.app_state.clone();
3379            crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
3380                .detach_and_log_err(cx);
3381        }
3382
3383        // if you're already following, find the right pane and focus it.
3384        if let Some(follower_state) = self.follower_states.get(&leader_id) {
3385            cx.focus_view(follower_state.pane());
3386            return;
3387        }
3388
3389        // Otherwise, follow.
3390        if let Some(task) = self.start_following(leader_id, cx) {
3391            task.detach_and_log_err(cx)
3392        }
3393    }
3394
3395    pub fn unfollow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3396        cx.notify();
3397        let state = self.follower_states.remove(&leader_id)?;
3398        for (_, item) in state.items_by_leader_view_id {
3399            item.view.set_leader_peer_id(None, cx);
3400        }
3401
3402        let project_id = self.project.read(cx).remote_id();
3403        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
3404        self.app_state
3405            .client
3406            .send(proto::Unfollow {
3407                room_id,
3408                project_id,
3409                leader_id: Some(leader_id),
3410            })
3411            .log_err();
3412
3413        Some(())
3414    }
3415
3416    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
3417        self.follower_states.contains_key(&peer_id)
3418    }
3419
3420    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
3421        cx.emit(Event::ActiveItemChanged);
3422        let active_entry = self.active_project_path(cx);
3423        self.project
3424            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
3425
3426        self.update_window_title(cx);
3427    }
3428
3429    fn update_window_title(&mut self, cx: &mut WindowContext) {
3430        let project = self.project().read(cx);
3431        let mut title = String::new();
3432
3433        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
3434            let filename = path
3435                .path
3436                .file_name()
3437                .map(|s| s.to_string_lossy())
3438                .or_else(|| {
3439                    Some(Cow::Borrowed(
3440                        project
3441                            .worktree_for_id(path.worktree_id, cx)?
3442                            .read(cx)
3443                            .root_name(),
3444                    ))
3445                });
3446
3447            if let Some(filename) = filename {
3448                title.push_str(filename.as_ref());
3449                title.push_str("");
3450            }
3451        }
3452
3453        for (i, name) in project.worktree_root_names(cx).enumerate() {
3454            if i > 0 {
3455                title.push_str(", ");
3456            }
3457            title.push_str(name);
3458        }
3459
3460        if title.is_empty() {
3461            title = "empty project".to_string();
3462        }
3463
3464        if project.is_via_collab() {
3465            title.push_str("");
3466        } else if project.is_shared() {
3467            title.push_str("");
3468        }
3469
3470        cx.set_window_title(&title);
3471    }
3472
3473    fn update_window_edited(&mut self, cx: &mut WindowContext) {
3474        let is_edited = !self.project.read(cx).is_disconnected(cx)
3475            && self
3476                .items(cx)
3477                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
3478        if is_edited != self.window_edited {
3479            self.window_edited = is_edited;
3480            cx.set_window_edited(self.window_edited)
3481        }
3482    }
3483
3484    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
3485        if self.notifications.is_empty() {
3486            None
3487        } else {
3488            Some(
3489                div()
3490                    .absolute()
3491                    .right_3()
3492                    .bottom_3()
3493                    .w_112()
3494                    .h_full()
3495                    .flex()
3496                    .flex_col()
3497                    .justify_end()
3498                    .gap_2()
3499                    .children(
3500                        self.notifications
3501                            .iter()
3502                            .map(|(_, notification)| notification.to_any()),
3503                    ),
3504            )
3505        }
3506    }
3507
3508    // RPC handlers
3509
3510    fn active_view_for_follower(
3511        &self,
3512        follower_project_id: Option<u64>,
3513        cx: &mut ViewContext<Self>,
3514    ) -> Option<proto::View> {
3515        let (item, panel_id) = self.active_item_for_followers(cx);
3516        let item = item?;
3517        let leader_id = self
3518            .pane_for(&*item)
3519            .and_then(|pane| self.leader_for_pane(&pane));
3520
3521        let item_handle = item.to_followable_item_handle(cx)?;
3522        let id = item_handle.remote_id(&self.app_state.client, cx)?;
3523        let variant = item_handle.to_state_proto(cx)?;
3524
3525        if item_handle.is_project_item(cx)
3526            && (follower_project_id.is_none()
3527                || follower_project_id != self.project.read(cx).remote_id())
3528        {
3529            return None;
3530        }
3531
3532        Some(proto::View {
3533            id: Some(id.to_proto()),
3534            leader_id,
3535            variant: Some(variant),
3536            panel_id: panel_id.map(|id| id as i32),
3537        })
3538    }
3539
3540    fn handle_follow(
3541        &mut self,
3542        follower_project_id: Option<u64>,
3543        cx: &mut ViewContext<Self>,
3544    ) -> proto::FollowResponse {
3545        let active_view = self.active_view_for_follower(follower_project_id, cx);
3546
3547        cx.notify();
3548        proto::FollowResponse {
3549            // TODO: Remove after version 0.145.x stabilizes.
3550            active_view_id: active_view.as_ref().and_then(|view| view.id.clone()),
3551            views: active_view.iter().cloned().collect(),
3552            active_view,
3553        }
3554    }
3555
3556    fn handle_update_followers(
3557        &mut self,
3558        leader_id: PeerId,
3559        message: proto::UpdateFollowers,
3560        _cx: &mut ViewContext<Self>,
3561    ) {
3562        self.leader_updates_tx
3563            .unbounded_send((leader_id, message))
3564            .ok();
3565    }
3566
3567    async fn process_leader_update(
3568        this: &WeakView<Self>,
3569        leader_id: PeerId,
3570        update: proto::UpdateFollowers,
3571        cx: &mut AsyncWindowContext,
3572    ) -> Result<()> {
3573        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
3574            proto::update_followers::Variant::CreateView(view) => {
3575                let view_id = ViewId::from_proto(view.id.clone().context("invalid view id")?)?;
3576                let should_add_view = this.update(cx, |this, _| {
3577                    if let Some(state) = this.follower_states.get_mut(&leader_id) {
3578                        anyhow::Ok(!state.items_by_leader_view_id.contains_key(&view_id))
3579                    } else {
3580                        anyhow::Ok(false)
3581                    }
3582                })??;
3583
3584                if should_add_view {
3585                    Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
3586                }
3587            }
3588            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
3589                let should_add_view = this.update(cx, |this, _| {
3590                    if let Some(state) = this.follower_states.get_mut(&leader_id) {
3591                        state.active_view_id = update_active_view
3592                            .view
3593                            .as_ref()
3594                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
3595
3596                        if state.active_view_id.is_some_and(|view_id| {
3597                            !state.items_by_leader_view_id.contains_key(&view_id)
3598                        }) {
3599                            anyhow::Ok(true)
3600                        } else {
3601                            anyhow::Ok(false)
3602                        }
3603                    } else {
3604                        anyhow::Ok(false)
3605                    }
3606                })??;
3607
3608                if should_add_view {
3609                    if let Some(view) = update_active_view.view {
3610                        Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
3611                    }
3612                }
3613            }
3614            proto::update_followers::Variant::UpdateView(update_view) => {
3615                let variant = update_view
3616                    .variant
3617                    .ok_or_else(|| anyhow!("missing update view variant"))?;
3618                let id = update_view
3619                    .id
3620                    .ok_or_else(|| anyhow!("missing update view id"))?;
3621                let mut tasks = Vec::new();
3622                this.update(cx, |this, cx| {
3623                    let project = this.project.clone();
3624                    if let Some(state) = this.follower_states.get(&leader_id) {
3625                        let view_id = ViewId::from_proto(id.clone())?;
3626                        if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
3627                            tasks.push(item.view.apply_update_proto(&project, variant.clone(), cx));
3628                        }
3629                    }
3630                    anyhow::Ok(())
3631                })??;
3632                try_join_all(tasks).await.log_err();
3633            }
3634        }
3635        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
3636        Ok(())
3637    }
3638
3639    async fn add_view_from_leader(
3640        this: WeakView<Self>,
3641        leader_id: PeerId,
3642        view: &proto::View,
3643        cx: &mut AsyncWindowContext,
3644    ) -> Result<()> {
3645        let this = this.upgrade().context("workspace dropped")?;
3646
3647        let Some(id) = view.id.clone() else {
3648            return Err(anyhow!("no id for view"));
3649        };
3650        let id = ViewId::from_proto(id)?;
3651        let panel_id = view.panel_id.and_then(proto::PanelId::from_i32);
3652
3653        let pane = this.update(cx, |this, _cx| {
3654            let state = this
3655                .follower_states
3656                .get(&leader_id)
3657                .context("stopped following")?;
3658            anyhow::Ok(state.pane().clone())
3659        })??;
3660        let existing_item = pane.update(cx, |pane, cx| {
3661            let client = this.read(cx).client().clone();
3662            pane.items().find_map(|item| {
3663                let item = item.to_followable_item_handle(cx)?;
3664                if item.remote_id(&client, cx) == Some(id) {
3665                    Some(item)
3666                } else {
3667                    None
3668                }
3669            })
3670        })?;
3671        let item = if let Some(existing_item) = existing_item {
3672            existing_item
3673        } else {
3674            let variant = view.variant.clone();
3675            if variant.is_none() {
3676                Err(anyhow!("missing view variant"))?;
3677            }
3678
3679            let task = cx.update(|cx| {
3680                FollowableViewRegistry::from_state_proto(this.clone(), id, variant, cx)
3681            })?;
3682
3683            let Some(task) = task else {
3684                return Err(anyhow!(
3685                    "failed to construct view from leader (maybe from a different version of zed?)"
3686                ));
3687            };
3688
3689            let mut new_item = task.await?;
3690            pane.update(cx, |pane, cx| {
3691                let mut item_ix_to_remove = None;
3692                for (ix, item) in pane.items().enumerate() {
3693                    if let Some(item) = item.to_followable_item_handle(cx) {
3694                        match new_item.dedup(item.as_ref(), cx) {
3695                            Some(item::Dedup::KeepExisting) => {
3696                                new_item =
3697                                    item.boxed_clone().to_followable_item_handle(cx).unwrap();
3698                                break;
3699                            }
3700                            Some(item::Dedup::ReplaceExisting) => {
3701                                item_ix_to_remove = Some(ix);
3702                                break;
3703                            }
3704                            None => {}
3705                        }
3706                    }
3707                }
3708
3709                if let Some(ix) = item_ix_to_remove {
3710                    pane.remove_item(ix, false, false, cx);
3711                    pane.add_item(new_item.boxed_clone(), false, false, Some(ix), cx);
3712                }
3713            })?;
3714
3715            new_item
3716        };
3717
3718        this.update(cx, |this, cx| {
3719            let state = this.follower_states.get_mut(&leader_id)?;
3720            item.set_leader_peer_id(Some(leader_id), cx);
3721            state.items_by_leader_view_id.insert(
3722                id,
3723                FollowerView {
3724                    view: item,
3725                    location: panel_id,
3726                },
3727            );
3728
3729            Some(())
3730        })?;
3731
3732        Ok(())
3733    }
3734
3735    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3736        let mut is_project_item = true;
3737        let mut update = proto::UpdateActiveView::default();
3738        if cx.is_window_active() {
3739            let (active_item, panel_id) = self.active_item_for_followers(cx);
3740
3741            if let Some(item) = active_item {
3742                if item.focus_handle(cx).contains_focused(cx) {
3743                    let leader_id = self
3744                        .pane_for(&*item)
3745                        .and_then(|pane| self.leader_for_pane(&pane));
3746
3747                    if let Some(item) = item.to_followable_item_handle(cx) {
3748                        let id = item
3749                            .remote_id(&self.app_state.client, cx)
3750                            .map(|id| id.to_proto());
3751
3752                        if let Some(id) = id.clone() {
3753                            if let Some(variant) = item.to_state_proto(cx) {
3754                                let view = Some(proto::View {
3755                                    id: Some(id.clone()),
3756                                    leader_id,
3757                                    variant: Some(variant),
3758                                    panel_id: panel_id.map(|id| id as i32),
3759                                });
3760
3761                                is_project_item = item.is_project_item(cx);
3762                                update = proto::UpdateActiveView {
3763                                    view,
3764                                    // TODO: Remove after version 0.145.x stabilizes.
3765                                    id: Some(id.clone()),
3766                                    leader_id,
3767                                };
3768                            }
3769                        };
3770                    }
3771                }
3772            }
3773        }
3774
3775        let active_view_id = update.view.as_ref().and_then(|view| view.id.as_ref());
3776        if active_view_id != self.last_active_view_id.as_ref() {
3777            self.last_active_view_id = active_view_id.cloned();
3778            self.update_followers(
3779                is_project_item,
3780                proto::update_followers::Variant::UpdateActiveView(update),
3781                cx,
3782            );
3783        }
3784    }
3785
3786    fn active_item_for_followers(
3787        &self,
3788        cx: &mut WindowContext,
3789    ) -> (Option<Box<dyn ItemHandle>>, Option<proto::PanelId>) {
3790        let mut active_item = None;
3791        let mut panel_id = None;
3792        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
3793            if dock.focus_handle(cx).contains_focused(cx) {
3794                if let Some(panel) = dock.read(cx).active_panel() {
3795                    if let Some(pane) = panel.pane(cx) {
3796                        if let Some(item) = pane.read(cx).active_item() {
3797                            active_item = Some(item);
3798                            panel_id = panel.remote_id();
3799                            break;
3800                        }
3801                    }
3802                }
3803            }
3804        }
3805
3806        if active_item.is_none() {
3807            active_item = self.active_pane().read(cx).active_item();
3808        }
3809        (active_item, panel_id)
3810    }
3811
3812    fn update_followers(
3813        &self,
3814        project_only: bool,
3815        update: proto::update_followers::Variant,
3816        cx: &mut WindowContext,
3817    ) -> Option<()> {
3818        // If this update only applies to for followers in the current project,
3819        // then skip it unless this project is shared. If it applies to all
3820        // followers, regardless of project, then set `project_id` to none,
3821        // indicating that it goes to all followers.
3822        let project_id = if project_only {
3823            Some(self.project.read(cx).remote_id()?)
3824        } else {
3825            None
3826        };
3827        self.app_state().workspace_store.update(cx, |store, cx| {
3828            store.update_followers(project_id, update, cx)
3829        })
3830    }
3831
3832    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3833        self.follower_states.iter().find_map(|(leader_id, state)| {
3834            if state.center_pane == *pane || state.dock_pane.as_ref() == Some(pane) {
3835                Some(*leader_id)
3836            } else {
3837                None
3838            }
3839        })
3840    }
3841
3842    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3843        cx.notify();
3844
3845        let call = self.active_call()?;
3846        let room = call.read(cx).room()?.read(cx);
3847        let participant = room.remote_participant_for_peer_id(leader_id)?;
3848
3849        let leader_in_this_app;
3850        let leader_in_this_project;
3851        match participant.location {
3852            call::ParticipantLocation::SharedProject { project_id } => {
3853                leader_in_this_app = true;
3854                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3855            }
3856            call::ParticipantLocation::UnsharedProject => {
3857                leader_in_this_app = true;
3858                leader_in_this_project = false;
3859            }
3860            call::ParticipantLocation::External => {
3861                leader_in_this_app = false;
3862                leader_in_this_project = false;
3863            }
3864        };
3865
3866        let state = self.follower_states.get(&leader_id)?;
3867        let mut item_to_activate = None;
3868        if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3869            if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3870                if leader_in_this_project || !item.view.is_project_item(cx) {
3871                    item_to_activate = Some((item.location, item.view.boxed_clone()));
3872                }
3873            }
3874        } else if let Some(shared_screen) =
3875            self.shared_screen_for_peer(leader_id, &state.center_pane, cx)
3876        {
3877            item_to_activate = Some((None, Box::new(shared_screen)));
3878        }
3879
3880        let (panel_id, item) = item_to_activate?;
3881
3882        let mut transfer_focus = state.center_pane.read(cx).has_focus(cx);
3883        let pane;
3884        if let Some(panel_id) = panel_id {
3885            pane = self.activate_panel_for_proto_id(panel_id, cx)?.pane(cx)?;
3886            let state = self.follower_states.get_mut(&leader_id)?;
3887            state.dock_pane = Some(pane.clone());
3888        } else {
3889            pane = state.center_pane.clone();
3890            let state = self.follower_states.get_mut(&leader_id)?;
3891            if let Some(dock_pane) = state.dock_pane.take() {
3892                transfer_focus |= dock_pane.focus_handle(cx).contains_focused(cx);
3893            }
3894        }
3895
3896        pane.update(cx, |pane, cx| {
3897            let focus_active_item = pane.has_focus(cx) || transfer_focus;
3898            if let Some(index) = pane.index_for_item(item.as_ref()) {
3899                pane.activate_item(index, false, false, cx);
3900            } else {
3901                pane.add_item(item.boxed_clone(), false, false, None, cx)
3902            }
3903
3904            if focus_active_item {
3905                pane.focus_active_item(cx)
3906            }
3907        });
3908
3909        None
3910    }
3911
3912    fn shared_screen_for_peer(
3913        &self,
3914        peer_id: PeerId,
3915        pane: &View<Pane>,
3916        cx: &mut WindowContext,
3917    ) -> Option<View<SharedScreen>> {
3918        let call = self.active_call()?;
3919        let room = call.read(cx).room()?.read(cx);
3920        let participant = room.remote_participant_for_peer_id(peer_id)?;
3921        let track = participant.video_tracks.values().next()?.clone();
3922        let user = participant.user.clone();
3923
3924        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3925            if item.read(cx).peer_id == peer_id {
3926                return Some(item);
3927            }
3928        }
3929
3930        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3931    }
3932
3933    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3934        if cx.is_window_active() {
3935            self.update_active_view_for_followers(cx);
3936
3937            if let Some(database_id) = self.database_id {
3938                cx.background_executor()
3939                    .spawn(persistence::DB.update_timestamp(database_id))
3940                    .detach();
3941            }
3942        } else {
3943            for pane in &self.panes {
3944                pane.update(cx, |pane, cx| {
3945                    if let Some(item) = pane.active_item() {
3946                        item.workspace_deactivated(cx);
3947                    }
3948                    for item in pane.items() {
3949                        if matches!(
3950                            item.workspace_settings(cx).autosave,
3951                            AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3952                        ) {
3953                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3954                                .detach_and_log_err(cx);
3955                        }
3956                    }
3957                });
3958            }
3959        }
3960    }
3961
3962    fn active_call(&self) -> Option<&Model<ActiveCall>> {
3963        self.active_call.as_ref().map(|(call, _)| call)
3964    }
3965
3966    fn on_active_call_event(
3967        &mut self,
3968        _: Model<ActiveCall>,
3969        event: &call::room::Event,
3970        cx: &mut ViewContext<Self>,
3971    ) {
3972        match event {
3973            call::room::Event::ParticipantLocationChanged { participant_id }
3974            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3975                self.leader_updated(*participant_id, cx);
3976            }
3977            _ => {}
3978        }
3979    }
3980
3981    pub fn database_id(&self) -> Option<WorkspaceId> {
3982        self.database_id
3983    }
3984
3985    fn local_paths(&self, cx: &AppContext) -> Option<Vec<Arc<Path>>> {
3986        let project = self.project().read(cx);
3987
3988        if project.is_local() {
3989            Some(
3990                project
3991                    .visible_worktrees(cx)
3992                    .map(|worktree| worktree.read(cx).abs_path())
3993                    .collect::<Vec<_>>(),
3994            )
3995        } else {
3996            None
3997        }
3998    }
3999
4000    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
4001        match member {
4002            Member::Axis(PaneAxis { members, .. }) => {
4003                for child in members.iter() {
4004                    self.remove_panes(child.clone(), cx)
4005                }
4006            }
4007            Member::Pane(pane) => {
4008                self.force_remove_pane(&pane, &None, cx);
4009            }
4010        }
4011    }
4012
4013    fn remove_from_session(&mut self, cx: &mut WindowContext) -> Task<()> {
4014        self.session_id.take();
4015        self.serialize_workspace_internal(cx)
4016    }
4017
4018    fn force_remove_pane(
4019        &mut self,
4020        pane: &View<Pane>,
4021        focus_on: &Option<View<Pane>>,
4022        cx: &mut ViewContext<Workspace>,
4023    ) {
4024        self.panes.retain(|p| p != pane);
4025        if let Some(focus_on) = focus_on {
4026            focus_on.update(cx, |pane, cx| pane.focus(cx));
4027        } else {
4028            self.panes
4029                .last()
4030                .unwrap()
4031                .update(cx, |pane, cx| pane.focus(cx));
4032        }
4033        if self.last_active_center_pane == Some(pane.downgrade()) {
4034            self.last_active_center_pane = None;
4035        }
4036        cx.notify();
4037    }
4038
4039    fn serialize_workspace(&mut self, cx: &mut ViewContext<Self>) {
4040        if self._schedule_serialize.is_none() {
4041            self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
4042                cx.background_executor()
4043                    .timer(Duration::from_millis(100))
4044                    .await;
4045                this.update(&mut cx, |this, cx| {
4046                    this.serialize_workspace_internal(cx).detach();
4047                    this._schedule_serialize.take();
4048                })
4049                .log_err();
4050            }));
4051        }
4052    }
4053
4054    fn serialize_workspace_internal(&self, cx: &mut WindowContext) -> Task<()> {
4055        let Some(database_id) = self.database_id() else {
4056            return Task::ready(());
4057        };
4058
4059        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
4060            let (items, active, pinned_count) = {
4061                let pane = pane_handle.read(cx);
4062                let active_item_id = pane.active_item().map(|item| item.item_id());
4063                (
4064                    pane.items()
4065                        .filter_map(|handle| {
4066                            let handle = handle.to_serializable_item_handle(cx)?;
4067
4068                            Some(SerializedItem {
4069                                kind: Arc::from(handle.serialized_item_kind()),
4070                                item_id: handle.item_id().as_u64(),
4071                                active: Some(handle.item_id()) == active_item_id,
4072                                preview: pane.is_active_preview_item(handle.item_id()),
4073                            })
4074                        })
4075                        .collect::<Vec<_>>(),
4076                    pane.has_focus(cx),
4077                    pane.pinned_count(),
4078                )
4079            };
4080
4081            SerializedPane::new(items, active, pinned_count)
4082        }
4083
4084        fn build_serialized_pane_group(
4085            pane_group: &Member,
4086            cx: &WindowContext,
4087        ) -> SerializedPaneGroup {
4088            match pane_group {
4089                Member::Axis(PaneAxis {
4090                    axis,
4091                    members,
4092                    flexes,
4093                    bounding_boxes: _,
4094                }) => SerializedPaneGroup::Group {
4095                    axis: SerializedAxis(*axis),
4096                    children: members
4097                        .iter()
4098                        .map(|member| build_serialized_pane_group(member, cx))
4099                        .collect::<Vec<_>>(),
4100                    flexes: Some(flexes.lock().clone()),
4101                },
4102                Member::Pane(pane_handle) => {
4103                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
4104                }
4105            }
4106        }
4107
4108        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
4109            let left_dock = this.left_dock.read(cx);
4110            let left_visible = left_dock.is_open();
4111            let left_active_panel = left_dock
4112                .visible_panel()
4113                .map(|panel| panel.persistent_name().to_string());
4114            let left_dock_zoom = left_dock
4115                .visible_panel()
4116                .map(|panel| panel.is_zoomed(cx))
4117                .unwrap_or(false);
4118
4119            let right_dock = this.right_dock.read(cx);
4120            let right_visible = right_dock.is_open();
4121            let right_active_panel = right_dock
4122                .visible_panel()
4123                .map(|panel| panel.persistent_name().to_string());
4124            let right_dock_zoom = right_dock
4125                .visible_panel()
4126                .map(|panel| panel.is_zoomed(cx))
4127                .unwrap_or(false);
4128
4129            let bottom_dock = this.bottom_dock.read(cx);
4130            let bottom_visible = bottom_dock.is_open();
4131            let bottom_active_panel = bottom_dock
4132                .visible_panel()
4133                .map(|panel| panel.persistent_name().to_string());
4134            let bottom_dock_zoom = bottom_dock
4135                .visible_panel()
4136                .map(|panel| panel.is_zoomed(cx))
4137                .unwrap_or(false);
4138
4139            DockStructure {
4140                left: DockData {
4141                    visible: left_visible,
4142                    active_panel: left_active_panel,
4143                    zoom: left_dock_zoom,
4144                },
4145                right: DockData {
4146                    visible: right_visible,
4147                    active_panel: right_active_panel,
4148                    zoom: right_dock_zoom,
4149                },
4150                bottom: DockData {
4151                    visible: bottom_visible,
4152                    active_panel: bottom_active_panel,
4153                    zoom: bottom_dock_zoom,
4154                },
4155            }
4156        }
4157
4158        let location = if let Some(ssh_project) = &self.serialized_ssh_project {
4159            Some(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
4160        } else if let Some(local_paths) = self.local_paths(cx) {
4161            if !local_paths.is_empty() {
4162                Some(SerializedWorkspaceLocation::from_local_paths(local_paths))
4163            } else {
4164                None
4165            }
4166        } else {
4167            None
4168        };
4169
4170        if let Some(location) = location {
4171            let center_group = build_serialized_pane_group(&self.center.root, cx);
4172            let docks = build_serialized_docks(self, cx);
4173            let window_bounds = Some(SerializedWindowBounds(cx.window_bounds()));
4174            let serialized_workspace = SerializedWorkspace {
4175                id: database_id,
4176                location,
4177                center_group,
4178                window_bounds,
4179                display: Default::default(),
4180                docks,
4181                centered_layout: self.centered_layout,
4182                session_id: self.session_id.clone(),
4183                window_id: Some(cx.window_handle().window_id().as_u64()),
4184            };
4185            return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
4186        }
4187        Task::ready(())
4188    }
4189
4190    async fn serialize_items(
4191        this: &WeakView<Self>,
4192        items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
4193        cx: &mut AsyncWindowContext,
4194    ) -> Result<()> {
4195        const CHUNK_SIZE: usize = 200;
4196        const THROTTLE_TIME: Duration = Duration::from_millis(200);
4197
4198        let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
4199
4200        while let Some(items_received) = serializable_items.next().await {
4201            let unique_items =
4202                items_received
4203                    .into_iter()
4204                    .fold(HashMap::default(), |mut acc, item| {
4205                        acc.entry(item.item_id()).or_insert(item);
4206                        acc
4207                    });
4208
4209            // We use into_iter() here so that the references to the items are moved into
4210            // the tasks and not kept alive while we're sleeping.
4211            for (_, item) in unique_items.into_iter() {
4212                if let Ok(Some(task)) =
4213                    this.update(cx, |workspace, cx| item.serialize(workspace, false, cx))
4214                {
4215                    cx.background_executor()
4216                        .spawn(async move { task.await.log_err() })
4217                        .detach();
4218                }
4219            }
4220
4221            cx.background_executor().timer(THROTTLE_TIME).await;
4222        }
4223
4224        Ok(())
4225    }
4226
4227    pub(crate) fn enqueue_item_serialization(
4228        &mut self,
4229        item: Box<dyn SerializableItemHandle>,
4230    ) -> Result<()> {
4231        self.serializable_items_tx
4232            .unbounded_send(item)
4233            .map_err(|err| anyhow!("failed to send serializable item over channel: {}", err))
4234    }
4235
4236    pub(crate) fn load_workspace(
4237        serialized_workspace: SerializedWorkspace,
4238        paths_to_open: Vec<Option<ProjectPath>>,
4239        cx: &mut ViewContext<Workspace>,
4240    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
4241        cx.spawn(|workspace, mut cx| async move {
4242            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
4243
4244            let mut center_group = None;
4245            let mut center_items = None;
4246
4247            // Traverse the splits tree and add to things
4248            if let Some((group, active_pane, items)) = serialized_workspace
4249                .center_group
4250                .deserialize(
4251                    &project,
4252                    serialized_workspace.id,
4253                    workspace.clone(),
4254                    &mut cx,
4255                )
4256                .await
4257            {
4258                center_items = Some(items);
4259                center_group = Some((group, active_pane))
4260            }
4261
4262            let mut items_by_project_path = HashMap::default();
4263            let mut item_ids_by_kind = HashMap::default();
4264            let mut all_deserialized_items = Vec::default();
4265            cx.update(|cx| {
4266                for item in center_items.unwrap_or_default().into_iter().flatten() {
4267                    if let Some(serializable_item_handle) = item.to_serializable_item_handle(cx) {
4268                        item_ids_by_kind
4269                            .entry(serializable_item_handle.serialized_item_kind())
4270                            .or_insert(Vec::new())
4271                            .push(item.item_id().as_u64() as ItemId);
4272                    }
4273
4274                    if let Some(project_path) = item.project_path(cx) {
4275                        items_by_project_path.insert(project_path, item.clone());
4276                    }
4277                    all_deserialized_items.push(item);
4278                }
4279            })?;
4280
4281            let opened_items = paths_to_open
4282                .into_iter()
4283                .map(|path_to_open| {
4284                    path_to_open
4285                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
4286                })
4287                .collect::<Vec<_>>();
4288
4289            // Remove old panes from workspace panes list
4290            workspace.update(&mut cx, |workspace, cx| {
4291                if let Some((center_group, active_pane)) = center_group {
4292                    workspace.remove_panes(workspace.center.root.clone(), cx);
4293
4294                    // Swap workspace center group
4295                    workspace.center = PaneGroup::with_root(center_group);
4296                    if let Some(active_pane) = active_pane {
4297                        workspace.set_active_pane(&active_pane, cx);
4298                        cx.focus_self();
4299                    } else {
4300                        workspace.set_active_pane(&workspace.center.first_pane(), cx);
4301                    }
4302                }
4303
4304                let docks = serialized_workspace.docks;
4305
4306                for (dock, serialized_dock) in [
4307                    (&mut workspace.right_dock, docks.right),
4308                    (&mut workspace.left_dock, docks.left),
4309                    (&mut workspace.bottom_dock, docks.bottom),
4310                ]
4311                .iter_mut()
4312                {
4313                    dock.update(cx, |dock, cx| {
4314                        dock.serialized_dock = Some(serialized_dock.clone());
4315                        dock.restore_state(cx);
4316                    });
4317                }
4318
4319                cx.notify();
4320            })?;
4321
4322            // Clean up all the items that have _not_ been loaded. Our ItemIds aren't stable. That means
4323            // after loading the items, we might have different items and in order to avoid
4324            // the database filling up, we delete items that haven't been loaded now.
4325            //
4326            // The items that have been loaded, have been saved after they've been added to the workspace.
4327            let clean_up_tasks = workspace.update(&mut cx, |_, cx| {
4328                item_ids_by_kind
4329                    .into_iter()
4330                    .map(|(item_kind, loaded_items)| {
4331                        SerializableItemRegistry::cleanup(
4332                            item_kind,
4333                            serialized_workspace.id,
4334                            loaded_items,
4335                            cx,
4336                        )
4337                        .log_err()
4338                    })
4339                    .collect::<Vec<_>>()
4340            })?;
4341
4342            futures::future::join_all(clean_up_tasks).await;
4343
4344            workspace
4345                .update(&mut cx, |workspace, cx| {
4346                    // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
4347                    workspace.serialize_workspace_internal(cx).detach();
4348
4349                    // Ensure that we mark the window as edited if we did load dirty items
4350                    workspace.update_window_edited(cx);
4351                })
4352                .ok();
4353
4354            Ok(opened_items)
4355        })
4356    }
4357
4358    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
4359        self.add_workspace_actions_listeners(div, cx)
4360            .on_action(cx.listener(Self::close_inactive_items_and_panes))
4361            .on_action(cx.listener(Self::close_all_items_and_panes))
4362            .on_action(cx.listener(Self::save_all))
4363            .on_action(cx.listener(Self::send_keystrokes))
4364            .on_action(cx.listener(Self::add_folder_to_project))
4365            .on_action(cx.listener(Self::follow_next_collaborator))
4366            .on_action(cx.listener(Self::close_window))
4367            .on_action(cx.listener(Self::activate_pane_at_index))
4368            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
4369                let pane = workspace.active_pane().clone();
4370                workspace.unfollow_in_pane(&pane, cx);
4371            }))
4372            .on_action(cx.listener(|workspace, action: &Save, cx| {
4373                workspace
4374                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
4375                    .detach_and_prompt_err("Failed to save", cx, |_, _| None);
4376            }))
4377            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
4378                workspace
4379                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
4380                    .detach_and_prompt_err("Failed to save", cx, |_, _| None);
4381            }))
4382            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
4383                workspace
4384                    .save_active_item(SaveIntent::SaveAs, cx)
4385                    .detach_and_prompt_err("Failed to save", cx, |_, _| None);
4386            }))
4387            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
4388                workspace.activate_previous_pane(cx)
4389            }))
4390            .on_action(
4391                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
4392            )
4393            .on_action(
4394                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
4395                    workspace.activate_pane_in_direction(action.0, cx)
4396                }),
4397            )
4398            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
4399                workspace.swap_pane_in_direction(action.0, cx)
4400            }))
4401            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
4402                this.toggle_dock(DockPosition::Left, cx);
4403            }))
4404            .on_action(
4405                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
4406                    workspace.toggle_dock(DockPosition::Right, cx);
4407                }),
4408            )
4409            .on_action(
4410                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
4411                    workspace.toggle_dock(DockPosition::Bottom, cx);
4412                }),
4413            )
4414            .on_action(
4415                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
4416                    workspace.close_all_docks(cx);
4417                }),
4418            )
4419            .on_action(
4420                cx.listener(|workspace: &mut Workspace, _: &ClearAllNotifications, cx| {
4421                    workspace.clear_all_notifications(cx);
4422                }),
4423            )
4424            .on_action(
4425                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
4426                    workspace.reopen_closed_item(cx).detach();
4427                }),
4428            )
4429            .on_action(cx.listener(Workspace::toggle_centered_layout))
4430    }
4431
4432    #[cfg(any(test, feature = "test-support"))]
4433    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
4434        use node_runtime::NodeRuntime;
4435        use session::Session;
4436
4437        let client = project.read(cx).client();
4438        let user_store = project.read(cx).user_store();
4439
4440        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
4441        let session = cx.new_model(|cx| AppSession::new(Session::test(), cx));
4442        cx.activate_window();
4443        let app_state = Arc::new(AppState {
4444            languages: project.read(cx).languages().clone(),
4445            workspace_store,
4446            client,
4447            user_store,
4448            fs: project.read(cx).fs().clone(),
4449            build_window_options: |_, _| Default::default(),
4450            node_runtime: NodeRuntime::unavailable(),
4451            session,
4452        });
4453        let workspace = Self::new(Default::default(), project, app_state, cx);
4454        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
4455        workspace
4456    }
4457
4458    pub fn register_action<A: Action>(
4459        &mut self,
4460        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
4461    ) -> &mut Self {
4462        let callback = Arc::new(callback);
4463
4464        self.workspace_actions.push(Box::new(move |div, cx| {
4465            let callback = callback.clone();
4466            div.on_action(
4467                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
4468            )
4469        }));
4470        self
4471    }
4472
4473    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
4474        for action in self.workspace_actions.iter() {
4475            div = (action)(div, cx)
4476        }
4477        div
4478    }
4479
4480    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
4481        self.modal_layer.read(cx).has_active_modal()
4482    }
4483
4484    pub fn active_modal<V: ManagedView + 'static>(&self, cx: &AppContext) -> Option<View<V>> {
4485        self.modal_layer.read(cx).active_modal()
4486    }
4487
4488    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
4489    where
4490        B: FnOnce(&mut ViewContext<V>) -> V,
4491    {
4492        self.modal_layer
4493            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
4494    }
4495
4496    pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
4497        self.centered_layout = !self.centered_layout;
4498        if let Some(database_id) = self.database_id() {
4499            cx.background_executor()
4500                .spawn(DB.set_centered_layout(database_id, self.centered_layout))
4501                .detach_and_log_err(cx);
4502        }
4503        cx.notify();
4504    }
4505
4506    fn adjust_padding(padding: Option<f32>) -> f32 {
4507        padding
4508            .unwrap_or(Self::DEFAULT_PADDING)
4509            .clamp(0.0, Self::MAX_PADDING)
4510    }
4511
4512    fn render_dock(
4513        &self,
4514        position: DockPosition,
4515        dock: &View<Dock>,
4516        cx: &WindowContext,
4517    ) -> Option<Div> {
4518        if self.zoomed_position == Some(position) {
4519            return None;
4520        }
4521
4522        let leader_border = dock.read(cx).active_panel().and_then(|panel| {
4523            let pane = panel.pane(cx)?;
4524            let follower_states = &self.follower_states;
4525            leader_border_for_pane(follower_states, &pane, cx)
4526        });
4527
4528        Some(
4529            div()
4530                .flex()
4531                .flex_none()
4532                .overflow_hidden()
4533                .child(dock.clone())
4534                .children(leader_border),
4535        )
4536    }
4537}
4538
4539fn leader_border_for_pane(
4540    follower_states: &HashMap<PeerId, FollowerState>,
4541    pane: &View<Pane>,
4542    cx: &WindowContext,
4543) -> Option<Div> {
4544    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
4545        if state.pane() == pane {
4546            Some((*leader_id, state))
4547        } else {
4548            None
4549        }
4550    })?;
4551
4552    let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
4553    let leader = room.remote_participant_for_peer_id(leader_id)?;
4554
4555    let mut leader_color = cx
4556        .theme()
4557        .players()
4558        .color_for_participant(leader.participant_index.0)
4559        .cursor;
4560    leader_color.fade_out(0.3);
4561    Some(
4562        div()
4563            .absolute()
4564            .size_full()
4565            .left_0()
4566            .top_0()
4567            .border_2()
4568            .border_color(leader_color),
4569    )
4570}
4571
4572fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
4573    ZED_WINDOW_POSITION
4574        .zip(*ZED_WINDOW_SIZE)
4575        .map(|(position, size)| Bounds {
4576            origin: position,
4577            size,
4578        })
4579}
4580
4581fn open_items(
4582    serialized_workspace: Option<SerializedWorkspace>,
4583    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
4584    cx: &mut ViewContext<Workspace>,
4585) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
4586    let restored_items = serialized_workspace.map(|serialized_workspace| {
4587        Workspace::load_workspace(
4588            serialized_workspace,
4589            project_paths_to_open
4590                .iter()
4591                .map(|(_, project_path)| project_path)
4592                .cloned()
4593                .collect(),
4594            cx,
4595        )
4596    });
4597
4598    cx.spawn(|workspace, mut cx| async move {
4599        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
4600
4601        if let Some(restored_items) = restored_items {
4602            let restored_items = restored_items.await?;
4603
4604            let restored_project_paths = restored_items
4605                .iter()
4606                .filter_map(|item| {
4607                    cx.update(|cx| item.as_ref()?.project_path(cx))
4608                        .ok()
4609                        .flatten()
4610                })
4611                .collect::<HashSet<_>>();
4612
4613            for restored_item in restored_items {
4614                opened_items.push(restored_item.map(Ok));
4615            }
4616
4617            project_paths_to_open
4618                .iter_mut()
4619                .for_each(|(_, project_path)| {
4620                    if let Some(project_path_to_open) = project_path {
4621                        if restored_project_paths.contains(project_path_to_open) {
4622                            *project_path = None;
4623                        }
4624                    }
4625                });
4626        } else {
4627            for _ in 0..project_paths_to_open.len() {
4628                opened_items.push(None);
4629            }
4630        }
4631        assert!(opened_items.len() == project_paths_to_open.len());
4632
4633        let tasks =
4634            project_paths_to_open
4635                .into_iter()
4636                .enumerate()
4637                .map(|(ix, (abs_path, project_path))| {
4638                    let workspace = workspace.clone();
4639                    cx.spawn(|mut cx| async move {
4640                        let file_project_path = project_path?;
4641                        let abs_path_task = workspace.update(&mut cx, |workspace, cx| {
4642                            workspace.project().update(cx, |project, cx| {
4643                                project.resolve_abs_path(abs_path.to_string_lossy().as_ref(), cx)
4644                            })
4645                        });
4646
4647                        // We only want to open file paths here. If one of the items
4648                        // here is a directory, it was already opened further above
4649                        // with a `find_or_create_worktree`.
4650                        if let Ok(task) = abs_path_task {
4651                            if task.await.map_or(true, |p| p.is_file()) {
4652                                return Some((
4653                                    ix,
4654                                    workspace
4655                                        .update(&mut cx, |workspace, cx| {
4656                                            workspace.open_path(file_project_path, None, true, cx)
4657                                        })
4658                                        .log_err()?
4659                                        .await,
4660                                ));
4661                            }
4662                        }
4663                        None
4664                    })
4665                });
4666
4667        let tasks = tasks.collect::<Vec<_>>();
4668
4669        let tasks = futures::future::join_all(tasks);
4670        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
4671            opened_items[ix] = Some(path_open_result);
4672        }
4673
4674        Ok(opened_items)
4675    })
4676}
4677
4678enum ActivateInDirectionTarget {
4679    Pane(View<Pane>),
4680    Dock(View<Dock>),
4681}
4682
4683fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
4684    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";
4685
4686    workspace
4687        .update(cx, |workspace, cx| {
4688            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
4689                struct DatabaseFailedNotification;
4690
4691                workspace.show_notification_once(
4692                    NotificationId::unique::<DatabaseFailedNotification>(),
4693                    cx,
4694                    |cx| {
4695                        cx.new_view(|_| {
4696                            MessageNotification::new("Failed to load the database file.")
4697                                .with_click_message("File an issue")
4698                                .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
4699                        })
4700                    },
4701                );
4702            }
4703        })
4704        .log_err();
4705}
4706
4707impl FocusableView for Workspace {
4708    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4709        self.active_pane.focus_handle(cx)
4710    }
4711}
4712
4713#[derive(Clone, Render)]
4714struct DraggedDock(DockPosition);
4715
4716impl Render for Workspace {
4717    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4718        let mut context = KeyContext::new_with_defaults();
4719        context.add("Workspace");
4720        let centered_layout = self.centered_layout
4721            && self.center.panes().len() == 1
4722            && self.active_item(cx).is_some();
4723        let render_padding = |size| {
4724            (size > 0.0).then(|| {
4725                div()
4726                    .h_full()
4727                    .w(relative(size))
4728                    .bg(cx.theme().colors().editor_background)
4729                    .border_color(cx.theme().colors().pane_group_border)
4730            })
4731        };
4732        let paddings = if centered_layout {
4733            let settings = WorkspaceSettings::get_global(cx).centered_layout;
4734            (
4735                render_padding(Self::adjust_padding(settings.left_padding)),
4736                render_padding(Self::adjust_padding(settings.right_padding)),
4737            )
4738        } else {
4739            (None, None)
4740        };
4741        let ui_font = theme::setup_ui_font(cx);
4742
4743        let theme = cx.theme().clone();
4744        let colors = theme.colors();
4745
4746        client_side_decorations(
4747            self.actions(div(), cx)
4748                .key_context(context)
4749                .relative()
4750                .size_full()
4751                .flex()
4752                .flex_col()
4753                .font(ui_font)
4754                .gap_0()
4755                .justify_start()
4756                .items_start()
4757                .text_color(colors.text)
4758                .overflow_hidden()
4759                .children(self.titlebar_item.clone())
4760                .child(
4761                    div()
4762                        .size_full()
4763                        .relative()
4764                        .flex_1()
4765                        .flex()
4766                        .flex_col()
4767                        .child(
4768                            div()
4769                                .id("workspace")
4770                                .bg(colors.background)
4771                                .relative()
4772                                .flex_1()
4773                                .w_full()
4774                                .flex()
4775                                .flex_col()
4776                                .overflow_hidden()
4777                                .border_t_1()
4778                                .border_b_1()
4779                                .border_color(colors.border)
4780                                .child({
4781                                    let this = cx.view().clone();
4782                                    canvas(
4783                                        move |bounds, cx| {
4784                                            this.update(cx, |this, _cx| this.bounds = bounds)
4785                                        },
4786                                        |_, _, _| {},
4787                                    )
4788                                    .absolute()
4789                                    .size_full()
4790                                })
4791                                .when(self.zoomed.is_none(), |this| {
4792                                    this.on_drag_move(cx.listener(
4793                                        |workspace, e: &DragMoveEvent<DraggedDock>, cx| {
4794                                            match e.drag(cx).0 {
4795                                                DockPosition::Left => {
4796                                                    let size = e.event.position.x
4797                                                        - workspace.bounds.left();
4798                                                    workspace.left_dock.update(
4799                                                        cx,
4800                                                        |left_dock, cx| {
4801                                                            left_dock.resize_active_panel(
4802                                                                Some(size),
4803                                                                cx,
4804                                                            );
4805                                                        },
4806                                                    );
4807                                                }
4808                                                DockPosition::Right => {
4809                                                    let size = workspace.bounds.right()
4810                                                        - e.event.position.x;
4811                                                    workspace.right_dock.update(
4812                                                        cx,
4813                                                        |right_dock, cx| {
4814                                                            right_dock.resize_active_panel(
4815                                                                Some(size),
4816                                                                cx,
4817                                                            );
4818                                                        },
4819                                                    );
4820                                                }
4821                                                DockPosition::Bottom => {
4822                                                    let size = workspace.bounds.bottom()
4823                                                        - e.event.position.y;
4824                                                    workspace.bottom_dock.update(
4825                                                        cx,
4826                                                        |bottom_dock, cx| {
4827                                                            bottom_dock.resize_active_panel(
4828                                                                Some(size),
4829                                                                cx,
4830                                                            );
4831                                                        },
4832                                                    );
4833                                                }
4834                                            }
4835                                        },
4836                                    ))
4837                                })
4838                                .child(
4839                                    div()
4840                                        .flex()
4841                                        .flex_row()
4842                                        .h_full()
4843                                        // Left Dock
4844                                        .children(self.render_dock(
4845                                            DockPosition::Left,
4846                                            &self.left_dock,
4847                                            cx,
4848                                        ))
4849                                        // Panes
4850                                        .child(
4851                                            div()
4852                                                .flex()
4853                                                .flex_col()
4854                                                .flex_1()
4855                                                .overflow_hidden()
4856                                                .child(
4857                                                    h_flex()
4858                                                        .flex_1()
4859                                                        .when_some(paddings.0, |this, p| {
4860                                                            this.child(p.border_r_1())
4861                                                        })
4862                                                        .child(self.center.render(
4863                                                            &self.project,
4864                                                            &self.follower_states,
4865                                                            self.active_call(),
4866                                                            &self.active_pane,
4867                                                            self.zoomed.as_ref(),
4868                                                            &self.app_state,
4869                                                            cx,
4870                                                        ))
4871                                                        .when_some(paddings.1, |this, p| {
4872                                                            this.child(p.border_l_1())
4873                                                        }),
4874                                                )
4875                                                .children(self.render_dock(
4876                                                    DockPosition::Bottom,
4877                                                    &self.bottom_dock,
4878                                                    cx,
4879                                                )),
4880                                        )
4881                                        // Right Dock
4882                                        .children(self.render_dock(
4883                                            DockPosition::Right,
4884                                            &self.right_dock,
4885                                            cx,
4886                                        )),
4887                                )
4888                                .children(self.zoomed.as_ref().and_then(|view| {
4889                                    let zoomed_view = view.upgrade()?;
4890                                    let div = div()
4891                                        .occlude()
4892                                        .absolute()
4893                                        .overflow_hidden()
4894                                        .border_color(colors.border)
4895                                        .bg(colors.background)
4896                                        .child(zoomed_view)
4897                                        .inset_0()
4898                                        .shadow_lg();
4899
4900                                    Some(match self.zoomed_position {
4901                                        Some(DockPosition::Left) => div.right_2().border_r_1(),
4902                                        Some(DockPosition::Right) => div.left_2().border_l_1(),
4903                                        Some(DockPosition::Bottom) => div.top_2().border_t_1(),
4904                                        None => {
4905                                            div.top_2().bottom_2().left_2().right_2().border_1()
4906                                        }
4907                                    })
4908                                }))
4909                                .children(self.render_notifications(cx)),
4910                        )
4911                        .child(self.status_bar.clone())
4912                        .child(self.modal_layer.clone()),
4913                ),
4914            cx,
4915        )
4916    }
4917}
4918
4919impl WorkspaceStore {
4920    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4921        Self {
4922            workspaces: Default::default(),
4923            _subscriptions: vec![
4924                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4925                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4926            ],
4927            client,
4928        }
4929    }
4930
4931    pub fn update_followers(
4932        &self,
4933        project_id: Option<u64>,
4934        update: proto::update_followers::Variant,
4935        cx: &AppContext,
4936    ) -> Option<()> {
4937        let active_call = ActiveCall::try_global(cx)?;
4938        let room_id = active_call.read(cx).room()?.read(cx).id();
4939        self.client
4940            .send(proto::UpdateFollowers {
4941                room_id,
4942                project_id,
4943                variant: Some(update),
4944            })
4945            .log_err()
4946    }
4947
4948    pub async fn handle_follow(
4949        this: Model<Self>,
4950        envelope: TypedEnvelope<proto::Follow>,
4951        mut cx: AsyncAppContext,
4952    ) -> Result<proto::FollowResponse> {
4953        this.update(&mut cx, |this, cx| {
4954            let follower = Follower {
4955                project_id: envelope.payload.project_id,
4956                peer_id: envelope.original_sender_id()?,
4957            };
4958
4959            let mut response = proto::FollowResponse::default();
4960            this.workspaces.retain(|workspace| {
4961                workspace
4962                    .update(cx, |workspace, cx| {
4963                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4964                        if let Some(active_view) = handler_response.active_view.clone() {
4965                            if workspace.project.read(cx).remote_id() == follower.project_id {
4966                                response.active_view = Some(active_view)
4967                            }
4968                        }
4969                    })
4970                    .is_ok()
4971            });
4972
4973            Ok(response)
4974        })?
4975    }
4976
4977    async fn handle_update_followers(
4978        this: Model<Self>,
4979        envelope: TypedEnvelope<proto::UpdateFollowers>,
4980        mut cx: AsyncAppContext,
4981    ) -> Result<()> {
4982        let leader_id = envelope.original_sender_id()?;
4983        let update = envelope.payload;
4984
4985        this.update(&mut cx, |this, cx| {
4986            this.workspaces.retain(|workspace| {
4987                workspace
4988                    .update(cx, |workspace, cx| {
4989                        let project_id = workspace.project.read(cx).remote_id();
4990                        if update.project_id != project_id && update.project_id.is_some() {
4991                            return;
4992                        }
4993                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4994                    })
4995                    .is_ok()
4996            });
4997            Ok(())
4998        })?
4999    }
5000}
5001
5002impl ViewId {
5003    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
5004        Ok(Self {
5005            creator: message
5006                .creator
5007                .ok_or_else(|| anyhow!("creator is missing"))?,
5008            id: message.id,
5009        })
5010    }
5011
5012    pub(crate) fn to_proto(self) -> proto::ViewId {
5013        proto::ViewId {
5014            creator: Some(self.creator),
5015            id: self.id,
5016        }
5017    }
5018}
5019
5020impl FollowerState {
5021    fn pane(&self) -> &View<Pane> {
5022        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
5023    }
5024}
5025
5026pub trait WorkspaceHandle {
5027    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
5028}
5029
5030impl WorkspaceHandle for View<Workspace> {
5031    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
5032        self.read(cx)
5033            .worktrees(cx)
5034            .flat_map(|worktree| {
5035                let worktree_id = worktree.read(cx).id();
5036                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
5037                    worktree_id,
5038                    path: f.path.clone(),
5039                })
5040            })
5041            .collect::<Vec<_>>()
5042    }
5043}
5044
5045impl std::fmt::Debug for OpenPaths {
5046    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5047        f.debug_struct("OpenPaths")
5048            .field("paths", &self.paths)
5049            .finish()
5050    }
5051}
5052
5053pub fn activate_workspace_for_project(
5054    cx: &mut AppContext,
5055    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
5056) -> Option<WindowHandle<Workspace>> {
5057    for window in cx.windows() {
5058        let Some(workspace) = window.downcast::<Workspace>() else {
5059            continue;
5060        };
5061
5062        let predicate = workspace
5063            .update(cx, |workspace, cx| {
5064                let project = workspace.project.read(cx);
5065                if predicate(project, cx) {
5066                    cx.activate_window();
5067                    true
5068                } else {
5069                    false
5070                }
5071            })
5072            .log_err()
5073            .unwrap_or(false);
5074
5075        if predicate {
5076            return Some(workspace);
5077        }
5078    }
5079
5080    None
5081}
5082
5083pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
5084    DB.last_workspace().await.log_err().flatten()
5085}
5086
5087pub fn last_session_workspace_locations(
5088    last_session_id: &str,
5089    last_session_window_stack: Option<Vec<WindowId>>,
5090) -> Option<Vec<SerializedWorkspaceLocation>> {
5091    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
5092        .log_err()
5093}
5094
5095actions!(collab, [OpenChannelNotes]);
5096actions!(zed, [OpenLog]);
5097
5098async fn join_channel_internal(
5099    channel_id: ChannelId,
5100    app_state: &Arc<AppState>,
5101    requesting_window: Option<WindowHandle<Workspace>>,
5102    active_call: &Model<ActiveCall>,
5103    cx: &mut AsyncAppContext,
5104) -> Result<bool> {
5105    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
5106        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
5107            return (false, None);
5108        };
5109
5110        let already_in_channel = room.channel_id() == Some(channel_id);
5111        let should_prompt = room.is_sharing_project()
5112            && !room.remote_participants().is_empty()
5113            && !already_in_channel;
5114        let open_room = if already_in_channel {
5115            active_call.room().cloned()
5116        } else {
5117            None
5118        };
5119        (should_prompt, open_room)
5120    })?;
5121
5122    if let Some(room) = open_room {
5123        let task = room.update(cx, |room, cx| {
5124            if let Some((project, host)) = room.most_active_project(cx) {
5125                return Some(join_in_room_project(project, host, app_state.clone(), cx));
5126            }
5127
5128            None
5129        })?;
5130        if let Some(task) = task {
5131            task.await?;
5132        }
5133        return anyhow::Ok(true);
5134    }
5135
5136    if should_prompt {
5137        if let Some(workspace) = requesting_window {
5138            let answer = workspace
5139                .update(cx, |_, cx| {
5140                    cx.prompt(
5141                        PromptLevel::Warning,
5142                        "Do you want to switch channels?",
5143                        Some("Leaving this call will unshare your current project."),
5144                        &["Yes, Join Channel", "Cancel"],
5145                    )
5146                })?
5147                .await;
5148
5149            if answer == Ok(1) {
5150                return Ok(false);
5151            }
5152        } else {
5153            return Ok(false); // unreachable!() hopefully
5154        }
5155    }
5156
5157    let client = cx.update(|cx| active_call.read(cx).client())?;
5158
5159    let mut client_status = client.status();
5160
5161    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
5162    'outer: loop {
5163        let Some(status) = client_status.recv().await else {
5164            return Err(anyhow!("error connecting"));
5165        };
5166
5167        match status {
5168            Status::Connecting
5169            | Status::Authenticating
5170            | Status::Reconnecting
5171            | Status::Reauthenticating => continue,
5172            Status::Connected { .. } => break 'outer,
5173            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
5174            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
5175            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
5176                return Err(ErrorCode::Disconnected.into());
5177            }
5178        }
5179    }
5180
5181    let room = active_call
5182        .update(cx, |active_call, cx| {
5183            active_call.join_channel(channel_id, cx)
5184        })?
5185        .await?;
5186
5187    let Some(room) = room else {
5188        return anyhow::Ok(true);
5189    };
5190
5191    room.update(cx, |room, _| room.room_update_completed())?
5192        .await;
5193
5194    let task = room.update(cx, |room, cx| {
5195        if let Some((project, host)) = room.most_active_project(cx) {
5196            return Some(join_in_room_project(project, host, app_state.clone(), cx));
5197        }
5198
5199        // If you are the first to join a channel, see if you should share your project.
5200        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
5201            if let Some(workspace) = requesting_window {
5202                let project = workspace.update(cx, |workspace, cx| {
5203                    let project = workspace.project.read(cx);
5204
5205                    if !CallSettings::get_global(cx).share_on_join {
5206                        return None;
5207                    }
5208
5209                    if (project.is_local() || project.is_via_ssh())
5210                        && project.visible_worktrees(cx).any(|tree| {
5211                            tree.read(cx)
5212                                .root_entry()
5213                                .map_or(false, |entry| entry.is_dir())
5214                        })
5215                    {
5216                        Some(workspace.project.clone())
5217                    } else {
5218                        None
5219                    }
5220                });
5221                if let Ok(Some(project)) = project {
5222                    return Some(cx.spawn(|room, mut cx| async move {
5223                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
5224                            .await?;
5225                        Ok(())
5226                    }));
5227                }
5228            }
5229        }
5230
5231        None
5232    })?;
5233    if let Some(task) = task {
5234        task.await?;
5235        return anyhow::Ok(true);
5236    }
5237    anyhow::Ok(false)
5238}
5239
5240pub fn join_channel(
5241    channel_id: ChannelId,
5242    app_state: Arc<AppState>,
5243    requesting_window: Option<WindowHandle<Workspace>>,
5244    cx: &mut AppContext,
5245) -> Task<Result<()>> {
5246    let active_call = ActiveCall::global(cx);
5247    cx.spawn(|mut cx| async move {
5248        let result = join_channel_internal(
5249            channel_id,
5250            &app_state,
5251            requesting_window,
5252            &active_call,
5253            &mut cx,
5254        )
5255            .await;
5256
5257        // join channel succeeded, and opened a window
5258        if matches!(result, Ok(true)) {
5259            return anyhow::Ok(());
5260        }
5261
5262        // find an existing workspace to focus and show call controls
5263        let mut active_window =
5264            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
5265        if active_window.is_none() {
5266            // no open workspaces, make one to show the error in (blergh)
5267            let (window_handle, _) = cx
5268                .update(|cx| {
5269                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
5270                })?
5271                .await?;
5272
5273            if result.is_ok() {
5274                cx.update(|cx| {
5275                    cx.dispatch_action(&OpenChannelNotes);
5276                }).log_err();
5277            }
5278
5279            active_window = Some(window_handle);
5280        }
5281
5282        if let Err(err) = result {
5283            log::error!("failed to join channel: {}", err);
5284            if let Some(active_window) = active_window {
5285                active_window
5286                    .update(&mut cx, |_, cx| {
5287                        let detail: SharedString = match err.error_code() {
5288                            ErrorCode::SignedOut => {
5289                                "Please sign in to continue.".into()
5290                            }
5291                            ErrorCode::UpgradeRequired => {
5292                                "Your are running an unsupported version of Zed. Please update to continue.".into()
5293                            }
5294                            ErrorCode::NoSuchChannel => {
5295                                "No matching channel was found. Please check the link and try again.".into()
5296                            }
5297                            ErrorCode::Forbidden => {
5298                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
5299                            }
5300                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
5301                            _ => format!("{}\n\nPlease try again.", err).into(),
5302                        };
5303                        cx.prompt(
5304                            PromptLevel::Critical,
5305                            "Failed to join channel",
5306                            Some(&detail),
5307                            &["Ok"],
5308                        )
5309                    })?
5310                    .await
5311                    .ok();
5312            }
5313        }
5314
5315        // return ok, we showed the error to the user.
5316        anyhow::Ok(())
5317    })
5318}
5319
5320pub async fn get_any_active_workspace(
5321    app_state: Arc<AppState>,
5322    mut cx: AsyncAppContext,
5323) -> anyhow::Result<WindowHandle<Workspace>> {
5324    // find an existing workspace to focus and show call controls
5325    let active_window = activate_any_workspace_window(&mut cx);
5326    if active_window.is_none() {
5327        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
5328            .await?;
5329    }
5330    activate_any_workspace_window(&mut cx).context("could not open zed")
5331}
5332
5333fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
5334    cx.update(|cx| {
5335        if let Some(workspace_window) = cx
5336            .active_window()
5337            .and_then(|window| window.downcast::<Workspace>())
5338        {
5339            return Some(workspace_window);
5340        }
5341
5342        for window in cx.windows() {
5343            if let Some(workspace_window) = window.downcast::<Workspace>() {
5344                workspace_window
5345                    .update(cx, |_, cx| cx.activate_window())
5346                    .ok();
5347                return Some(workspace_window);
5348            }
5349        }
5350        None
5351    })
5352    .ok()
5353    .flatten()
5354}
5355
5356pub fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
5357    cx.windows()
5358        .into_iter()
5359        .filter_map(|window| window.downcast::<Workspace>())
5360        .filter(|workspace| {
5361            workspace
5362                .read(cx)
5363                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
5364        })
5365        .collect()
5366}
5367
5368#[derive(Default)]
5369pub struct OpenOptions {
5370    pub open_new_workspace: Option<bool>,
5371    pub replace_window: Option<WindowHandle<Workspace>>,
5372    pub env: Option<HashMap<String, String>>,
5373}
5374
5375#[allow(clippy::type_complexity)]
5376pub fn open_paths(
5377    abs_paths: &[PathBuf],
5378    app_state: Arc<AppState>,
5379    open_options: OpenOptions,
5380    cx: &mut AppContext,
5381) -> Task<
5382    anyhow::Result<(
5383        WindowHandle<Workspace>,
5384        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
5385    )>,
5386> {
5387    let abs_paths = abs_paths.to_vec();
5388    let mut existing = None;
5389    let mut best_match = None;
5390    let mut open_visible = OpenVisible::All;
5391
5392    if open_options.open_new_workspace != Some(true) {
5393        for window in local_workspace_windows(cx) {
5394            if let Ok(workspace) = window.read(cx) {
5395                let m = workspace
5396                    .project
5397                    .read(cx)
5398                    .visibility_for_paths(&abs_paths, cx);
5399                if m > best_match {
5400                    existing = Some(window);
5401                    best_match = m;
5402                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
5403                    existing = Some(window)
5404                }
5405            }
5406        }
5407    }
5408
5409    cx.spawn(move |mut cx| async move {
5410        if open_options.open_new_workspace.is_none() && existing.is_none() {
5411            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
5412            if futures::future::join_all(all_files)
5413                .await
5414                .into_iter()
5415                .filter_map(|result| result.ok().flatten())
5416                .all(|file| !file.is_dir)
5417            {
5418                cx.update(|cx| {
5419                    for window in local_workspace_windows(cx) {
5420                        if let Ok(workspace) = window.read(cx) {
5421                            let project = workspace.project().read(cx);
5422                            if project.is_via_collab() {
5423                                continue;
5424                            }
5425                            existing = Some(window);
5426                            open_visible = OpenVisible::None;
5427                            break;
5428                        }
5429                    }
5430                })?;
5431            }
5432        }
5433
5434        if let Some(existing) = existing {
5435            Ok((
5436                existing,
5437                existing
5438                    .update(&mut cx, |workspace, cx| {
5439                        cx.activate_window();
5440                        workspace.open_paths(abs_paths, open_visible, None, cx)
5441                    })?
5442                    .await,
5443            ))
5444        } else {
5445            cx.update(move |cx| {
5446                Workspace::new_local(
5447                    abs_paths,
5448                    app_state.clone(),
5449                    open_options.replace_window,
5450                    open_options.env,
5451                    cx,
5452                )
5453            })?
5454            .await
5455        }
5456    })
5457}
5458
5459pub fn open_new(
5460    open_options: OpenOptions,
5461    app_state: Arc<AppState>,
5462    cx: &mut AppContext,
5463    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
5464) -> Task<anyhow::Result<()>> {
5465    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
5466    cx.spawn(|mut cx| async move {
5467        let (workspace, opened_paths) = task.await?;
5468        workspace.update(&mut cx, |workspace, cx| {
5469            if opened_paths.is_empty() {
5470                init(workspace, cx)
5471            }
5472        })?;
5473        Ok(())
5474    })
5475}
5476
5477pub fn create_and_open_local_file(
5478    path: &'static Path,
5479    cx: &mut ViewContext<Workspace>,
5480    default_content: impl 'static + Send + FnOnce() -> Rope,
5481) -> Task<Result<Box<dyn ItemHandle>>> {
5482    cx.spawn(|workspace, mut cx| async move {
5483        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
5484        if !fs.is_file(path).await {
5485            fs.create_file(path, Default::default()).await?;
5486            fs.save(path, &default_content(), Default::default())
5487                .await?;
5488        }
5489
5490        let mut items = workspace
5491            .update(&mut cx, |workspace, cx| {
5492                workspace.with_local_workspace(cx, |workspace, cx| {
5493                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
5494                })
5495            })?
5496            .await?
5497            .await;
5498
5499        let item = items.pop().flatten();
5500        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
5501    })
5502}
5503
5504pub fn open_ssh_project(
5505    window: WindowHandle<Workspace>,
5506    connection_options: SshConnectionOptions,
5507    cancel_rx: oneshot::Receiver<()>,
5508    delegate: Arc<dyn SshClientDelegate>,
5509    app_state: Arc<AppState>,
5510    paths: Vec<PathBuf>,
5511    cx: &mut AppContext,
5512) -> Task<Result<()>> {
5513    cx.spawn(|mut cx| async move {
5514        let (serialized_ssh_project, workspace_id, serialized_workspace) =
5515            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
5516
5517        let session = match cx
5518            .update(|cx| {
5519                remote::SshRemoteClient::new(
5520                    ConnectionIdentifier::Workspace(workspace_id.0),
5521                    connection_options,
5522                    cancel_rx,
5523                    delegate,
5524                    cx,
5525                )
5526            })?
5527            .await?
5528        {
5529            Some(result) => result,
5530            None => return Ok(()),
5531        };
5532
5533        let project = cx.update(|cx| {
5534            project::Project::ssh(
5535                session,
5536                app_state.client.clone(),
5537                app_state.node_runtime.clone(),
5538                app_state.user_store.clone(),
5539                app_state.languages.clone(),
5540                app_state.fs.clone(),
5541                cx,
5542            )
5543        })?;
5544
5545        let toolchains = DB.toolchains(workspace_id).await?;
5546        for (toolchain, worktree_id) in toolchains {
5547            project
5548                .update(&mut cx, |this, cx| {
5549                    this.activate_toolchain(worktree_id, toolchain, cx)
5550                })?
5551                .await;
5552        }
5553        let mut project_paths_to_open = vec![];
5554        let mut project_path_errors = vec![];
5555
5556        for path in paths {
5557            let result = cx
5558                .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))?
5559                .await;
5560            match result {
5561                Ok((_, project_path)) => {
5562                    project_paths_to_open.push((path.clone(), Some(project_path)));
5563                }
5564                Err(error) => {
5565                    project_path_errors.push(error);
5566                }
5567            };
5568        }
5569
5570        if project_paths_to_open.is_empty() {
5571            return Err(project_path_errors
5572                .pop()
5573                .unwrap_or_else(|| anyhow!("no paths given")));
5574        }
5575
5576        cx.update_window(window.into(), |_, cx| {
5577            cx.replace_root_view(|cx| {
5578                let mut workspace =
5579                    Workspace::new(Some(workspace_id), project, app_state.clone(), cx);
5580
5581                workspace
5582                    .client()
5583                    .telemetry()
5584                    .report_app_event("open ssh project".to_string());
5585
5586                workspace.set_serialized_ssh_project(serialized_ssh_project);
5587                workspace
5588            });
5589        })?;
5590
5591        window
5592            .update(&mut cx, |_, cx| {
5593                cx.activate_window();
5594
5595                open_items(serialized_workspace, project_paths_to_open, cx)
5596            })?
5597            .await?;
5598
5599        window.update(&mut cx, |workspace, cx| {
5600            for error in project_path_errors {
5601                if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
5602                    if let Some(path) = error.error_tag("path") {
5603                        workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
5604                    }
5605                } else {
5606                    workspace.show_error(&error, cx)
5607                }
5608            }
5609        })
5610    })
5611}
5612
5613fn serialize_ssh_project(
5614    connection_options: SshConnectionOptions,
5615    paths: Vec<PathBuf>,
5616    cx: &AsyncAppContext,
5617) -> Task<
5618    Result<(
5619        SerializedSshProject,
5620        WorkspaceId,
5621        Option<SerializedWorkspace>,
5622    )>,
5623> {
5624    cx.background_executor().spawn(async move {
5625        let serialized_ssh_project = persistence::DB
5626            .get_or_create_ssh_project(
5627                connection_options.host.clone(),
5628                connection_options.port,
5629                paths
5630                    .iter()
5631                    .map(|path| path.to_string_lossy().to_string())
5632                    .collect::<Vec<_>>(),
5633                connection_options.username.clone(),
5634            )
5635            .await?;
5636
5637        let serialized_workspace =
5638            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
5639
5640        let workspace_id = if let Some(workspace_id) =
5641            serialized_workspace.as_ref().map(|workspace| workspace.id)
5642        {
5643            workspace_id
5644        } else {
5645            persistence::DB.next_id().await?
5646        };
5647
5648        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
5649    })
5650}
5651
5652pub fn join_in_room_project(
5653    project_id: u64,
5654    follow_user_id: u64,
5655    app_state: Arc<AppState>,
5656    cx: &mut AppContext,
5657) -> Task<Result<()>> {
5658    let windows = cx.windows();
5659    cx.spawn(|mut cx| async move {
5660        let existing_workspace = windows.into_iter().find_map(|window| {
5661            window.downcast::<Workspace>().and_then(|window| {
5662                window
5663                    .update(&mut cx, |workspace, cx| {
5664                        if workspace.project().read(cx).remote_id() == Some(project_id) {
5665                            Some(window)
5666                        } else {
5667                            None
5668                        }
5669                    })
5670                    .unwrap_or(None)
5671            })
5672        });
5673
5674        let workspace = if let Some(existing_workspace) = existing_workspace {
5675            existing_workspace
5676        } else {
5677            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
5678            let room = active_call
5679                .read_with(&cx, |call, _| call.room().cloned())?
5680                .ok_or_else(|| anyhow!("not in a call"))?;
5681            let project = room
5682                .update(&mut cx, |room, cx| {
5683                    room.join_project(
5684                        project_id,
5685                        app_state.languages.clone(),
5686                        app_state.fs.clone(),
5687                        cx,
5688                    )
5689                })?
5690                .await?;
5691
5692            let window_bounds_override = window_bounds_env_override();
5693            cx.update(|cx| {
5694                let mut options = (app_state.build_window_options)(None, cx);
5695                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
5696                cx.open_window(options, |cx| {
5697                    cx.new_view(|cx| {
5698                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5699                    })
5700                })
5701            })??
5702        };
5703
5704        workspace.update(&mut cx, |workspace, cx| {
5705            cx.activate(true);
5706            cx.activate_window();
5707
5708            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5709                let follow_peer_id = room
5710                    .read(cx)
5711                    .remote_participants()
5712                    .iter()
5713                    .find(|(_, participant)| participant.user.id == follow_user_id)
5714                    .map(|(_, p)| p.peer_id)
5715                    .or_else(|| {
5716                        // If we couldn't follow the given user, follow the host instead.
5717                        let collaborator = workspace
5718                            .project()
5719                            .read(cx)
5720                            .collaborators()
5721                            .values()
5722                            .find(|collaborator| collaborator.is_host)?;
5723                        Some(collaborator.peer_id)
5724                    });
5725
5726                if let Some(follow_peer_id) = follow_peer_id {
5727                    workspace.follow(follow_peer_id, cx);
5728                }
5729            }
5730        })?;
5731
5732        anyhow::Ok(())
5733    })
5734}
5735
5736pub fn reload(reload: &Reload, cx: &mut AppContext) {
5737    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5738    let mut workspace_windows = cx
5739        .windows()
5740        .into_iter()
5741        .filter_map(|window| window.downcast::<Workspace>())
5742        .collect::<Vec<_>>();
5743
5744    // If multiple windows have unsaved changes, and need a save prompt,
5745    // prompt in the active window before switching to a different window.
5746    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5747
5748    let mut prompt = None;
5749    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5750        prompt = window
5751            .update(cx, |_, cx| {
5752                cx.prompt(
5753                    PromptLevel::Info,
5754                    "Are you sure you want to restart?",
5755                    None,
5756                    &["Restart", "Cancel"],
5757                )
5758            })
5759            .ok();
5760    }
5761
5762    let binary_path = reload.binary_path.clone();
5763    cx.spawn(|mut cx| async move {
5764        if let Some(prompt) = prompt {
5765            let answer = prompt.await?;
5766            if answer != 0 {
5767                return Ok(());
5768            }
5769        }
5770
5771        // If the user cancels any save prompt, then keep the app open.
5772        for window in workspace_windows {
5773            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5774                workspace.prepare_to_close(CloseIntent::Quit, cx)
5775            }) {
5776                if !should_close.await? {
5777                    return Ok(());
5778                }
5779            }
5780        }
5781
5782        cx.update(|cx| cx.restart(binary_path))
5783    })
5784    .detach_and_log_err(cx);
5785}
5786
5787fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
5788    let mut parts = value.split(',');
5789    let x: usize = parts.next()?.parse().ok()?;
5790    let y: usize = parts.next()?.parse().ok()?;
5791    Some(point(px(x as f32), px(y as f32)))
5792}
5793
5794fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
5795    let mut parts = value.split(',');
5796    let width: usize = parts.next()?.parse().ok()?;
5797    let height: usize = parts.next()?.parse().ok()?;
5798    Some(size(px(width as f32), px(height as f32)))
5799}
5800
5801pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext) -> Stateful<Div> {
5802    const BORDER_SIZE: Pixels = px(1.0);
5803    let decorations = cx.window_decorations();
5804
5805    if matches!(decorations, Decorations::Client { .. }) {
5806        cx.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
5807    }
5808
5809    struct GlobalResizeEdge(ResizeEdge);
5810    impl Global for GlobalResizeEdge {}
5811
5812    div()
5813        .id("window-backdrop")
5814        .bg(transparent_black())
5815        .map(|div| match decorations {
5816            Decorations::Server => div,
5817            Decorations::Client { tiling, .. } => div
5818                .when(!(tiling.top || tiling.right), |div| {
5819                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5820                })
5821                .when(!(tiling.top || tiling.left), |div| {
5822                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5823                })
5824                .when(!(tiling.bottom || tiling.right), |div| {
5825                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5826                })
5827                .when(!(tiling.bottom || tiling.left), |div| {
5828                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5829                })
5830                .when(!tiling.top, |div| {
5831                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
5832                })
5833                .when(!tiling.bottom, |div| {
5834                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
5835                })
5836                .when(!tiling.left, |div| {
5837                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
5838                })
5839                .when(!tiling.right, |div| {
5840                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
5841                })
5842                .on_mouse_move(move |e, cx| {
5843                    let size = cx.window_bounds().get_bounds().size;
5844                    let pos = e.position;
5845
5846                    let new_edge =
5847                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
5848
5849                    let edge = cx.try_global::<GlobalResizeEdge>();
5850                    if new_edge != edge.map(|edge| edge.0) {
5851                        cx.window_handle()
5852                            .update(cx, |workspace, cx| cx.notify(workspace.entity_id()))
5853                            .ok();
5854                    }
5855                })
5856                .on_mouse_down(MouseButton::Left, move |e, cx| {
5857                    let size = cx.window_bounds().get_bounds().size;
5858                    let pos = e.position;
5859
5860                    let edge = match resize_edge(
5861                        pos,
5862                        theme::CLIENT_SIDE_DECORATION_SHADOW,
5863                        size,
5864                        tiling,
5865                    ) {
5866                        Some(value) => value,
5867                        None => return,
5868                    };
5869
5870                    cx.start_window_resize(edge);
5871                }),
5872        })
5873        .size_full()
5874        .child(
5875            div()
5876                .cursor(CursorStyle::Arrow)
5877                .map(|div| match decorations {
5878                    Decorations::Server => div,
5879                    Decorations::Client { tiling } => div
5880                        .border_color(cx.theme().colors().border)
5881                        .when(!(tiling.top || tiling.right), |div| {
5882                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5883                        })
5884                        .when(!(tiling.top || tiling.left), |div| {
5885                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5886                        })
5887                        .when(!(tiling.bottom || tiling.right), |div| {
5888                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5889                        })
5890                        .when(!(tiling.bottom || tiling.left), |div| {
5891                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5892                        })
5893                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
5894                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
5895                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
5896                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
5897                        .when(!tiling.is_tiled(), |div| {
5898                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
5899                                color: Hsla {
5900                                    h: 0.,
5901                                    s: 0.,
5902                                    l: 0.,
5903                                    a: 0.4,
5904                                },
5905                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
5906                                spread_radius: px(0.),
5907                                offset: point(px(0.0), px(0.0)),
5908                            }])
5909                        }),
5910                })
5911                .on_mouse_move(|_e, cx| {
5912                    cx.stop_propagation();
5913                })
5914                .size_full()
5915                .child(element),
5916        )
5917        .map(|div| match decorations {
5918            Decorations::Server => div,
5919            Decorations::Client { tiling, .. } => div.child(
5920                canvas(
5921                    |_bounds, cx| {
5922                        cx.insert_hitbox(
5923                            Bounds::new(
5924                                point(px(0.0), px(0.0)),
5925                                cx.window_bounds().get_bounds().size,
5926                            ),
5927                            false,
5928                        )
5929                    },
5930                    move |_bounds, hitbox, cx| {
5931                        let mouse = cx.mouse_position();
5932                        let size = cx.window_bounds().get_bounds().size;
5933                        let Some(edge) =
5934                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
5935                        else {
5936                            return;
5937                        };
5938                        cx.set_global(GlobalResizeEdge(edge));
5939                        cx.set_cursor_style(
5940                            match edge {
5941                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
5942                                ResizeEdge::Left | ResizeEdge::Right => {
5943                                    CursorStyle::ResizeLeftRight
5944                                }
5945                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
5946                                    CursorStyle::ResizeUpLeftDownRight
5947                                }
5948                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
5949                                    CursorStyle::ResizeUpRightDownLeft
5950                                }
5951                            },
5952                            &hitbox,
5953                        );
5954                    },
5955                )
5956                .size_full()
5957                .absolute(),
5958            ),
5959        })
5960}
5961
5962fn resize_edge(
5963    pos: Point<Pixels>,
5964    shadow_size: Pixels,
5965    window_size: Size<Pixels>,
5966    tiling: Tiling,
5967) -> Option<ResizeEdge> {
5968    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
5969    if bounds.contains(&pos) {
5970        return None;
5971    }
5972
5973    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
5974    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
5975    if !tiling.top && top_left_bounds.contains(&pos) {
5976        return Some(ResizeEdge::TopLeft);
5977    }
5978
5979    let top_right_bounds = Bounds::new(
5980        Point::new(window_size.width - corner_size.width, px(0.)),
5981        corner_size,
5982    );
5983    if !tiling.top && top_right_bounds.contains(&pos) {
5984        return Some(ResizeEdge::TopRight);
5985    }
5986
5987    let bottom_left_bounds = Bounds::new(
5988        Point::new(px(0.), window_size.height - corner_size.height),
5989        corner_size,
5990    );
5991    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
5992        return Some(ResizeEdge::BottomLeft);
5993    }
5994
5995    let bottom_right_bounds = Bounds::new(
5996        Point::new(
5997            window_size.width - corner_size.width,
5998            window_size.height - corner_size.height,
5999        ),
6000        corner_size,
6001    );
6002    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
6003        return Some(ResizeEdge::BottomRight);
6004    }
6005
6006    if !tiling.top && pos.y < shadow_size {
6007        Some(ResizeEdge::Top)
6008    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
6009        Some(ResizeEdge::Bottom)
6010    } else if !tiling.left && pos.x < shadow_size {
6011        Some(ResizeEdge::Left)
6012    } else if !tiling.right && pos.x > window_size.width - shadow_size {
6013        Some(ResizeEdge::Right)
6014    } else {
6015        None
6016    }
6017}
6018
6019fn join_pane_into_active(active_pane: &View<Pane>, pane: &View<Pane>, cx: &mut WindowContext<'_>) {
6020    if pane == active_pane {
6021        return;
6022    } else if pane.read(cx).items_len() == 0 {
6023        pane.update(cx, |_, cx| {
6024            cx.emit(pane::Event::Remove {
6025                focus_on_pane: None,
6026            });
6027        })
6028    } else {
6029        move_all_items(pane, active_pane, cx);
6030    }
6031}
6032
6033fn move_all_items(from_pane: &View<Pane>, to_pane: &View<Pane>, cx: &mut WindowContext<'_>) {
6034    let destination_is_different = from_pane != to_pane;
6035    let mut moved_items = 0;
6036    for (item_ix, item_handle) in from_pane
6037        .read(cx)
6038        .items()
6039        .enumerate()
6040        .map(|(ix, item)| (ix, item.clone()))
6041        .collect::<Vec<_>>()
6042    {
6043        let ix = item_ix - moved_items;
6044        if destination_is_different {
6045            // Close item from previous pane
6046            from_pane.update(cx, |source, cx| {
6047                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), cx);
6048            });
6049            moved_items += 1;
6050        }
6051
6052        // This automatically removes duplicate items in the pane
6053        to_pane.update(cx, |destination, cx| {
6054            destination.add_item(item_handle, true, true, None, cx);
6055            destination.focus(cx)
6056        });
6057    }
6058}
6059
6060pub fn move_item(
6061    source: &View<Pane>,
6062    destination: &View<Pane>,
6063    item_id_to_move: EntityId,
6064    destination_index: usize,
6065    cx: &mut WindowContext<'_>,
6066) {
6067    let Some((item_ix, item_handle)) = source
6068        .read(cx)
6069        .items()
6070        .enumerate()
6071        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
6072        .map(|(ix, item)| (ix, item.clone()))
6073    else {
6074        // Tab was closed during drag
6075        return;
6076    };
6077
6078    if source != destination {
6079        // Close item from previous pane
6080        source.update(cx, |source, cx| {
6081            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), cx);
6082        });
6083    }
6084
6085    // This automatically removes duplicate items in the pane
6086    destination.update(cx, |destination, cx| {
6087        destination.add_item(item_handle, true, true, Some(destination_index), cx);
6088        destination.focus(cx)
6089    });
6090}
6091
6092#[cfg(test)]
6093mod tests {
6094    use std::{cell::RefCell, rc::Rc};
6095
6096    use super::*;
6097    use crate::{
6098        dock::{test::TestPanel, PanelEvent},
6099        item::{
6100            test::{TestItem, TestProjectItem},
6101            ItemEvent,
6102        },
6103    };
6104    use fs::FakeFs;
6105    use gpui::{
6106        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
6107        UpdateGlobal, VisualTestContext,
6108    };
6109    use project::{Project, ProjectEntryId};
6110    use serde_json::json;
6111    use settings::SettingsStore;
6112
6113    #[gpui::test]
6114    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
6115        init_test(cx);
6116
6117        let fs = FakeFs::new(cx.executor());
6118        let project = Project::test(fs, [], cx).await;
6119        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6120
6121        // Adding an item with no ambiguity renders the tab without detail.
6122        let item1 = cx.new_view(|cx| {
6123            let mut item = TestItem::new(cx);
6124            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
6125            item
6126        });
6127        workspace.update(cx, |workspace, cx| {
6128            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6129        });
6130        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
6131
6132        // Adding an item that creates ambiguity increases the level of detail on
6133        // both tabs.
6134        let item2 = cx.new_view(|cx| {
6135            let mut item = TestItem::new(cx);
6136            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
6137            item
6138        });
6139        workspace.update(cx, |workspace, cx| {
6140            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6141        });
6142        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6143        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6144
6145        // Adding an item that creates ambiguity increases the level of detail only
6146        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
6147        // we stop at the highest detail available.
6148        let item3 = cx.new_view(|cx| {
6149            let mut item = TestItem::new(cx);
6150            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
6151            item
6152        });
6153        workspace.update(cx, |workspace, cx| {
6154            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6155        });
6156        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6157        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6158        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6159    }
6160
6161    #[gpui::test]
6162    async fn test_tracking_active_path(cx: &mut TestAppContext) {
6163        init_test(cx);
6164
6165        let fs = FakeFs::new(cx.executor());
6166        fs.insert_tree(
6167            "/root1",
6168            json!({
6169                "one.txt": "",
6170                "two.txt": "",
6171            }),
6172        )
6173        .await;
6174        fs.insert_tree(
6175            "/root2",
6176            json!({
6177                "three.txt": "",
6178            }),
6179        )
6180        .await;
6181
6182        let project = Project::test(fs, ["root1".as_ref()], cx).await;
6183        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6184        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6185        let worktree_id = project.update(cx, |project, cx| {
6186            project.worktrees(cx).next().unwrap().read(cx).id()
6187        });
6188
6189        let item1 = cx.new_view(|cx| {
6190            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
6191        });
6192        let item2 = cx.new_view(|cx| {
6193            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
6194        });
6195
6196        // Add an item to an empty pane
6197        workspace.update(cx, |workspace, cx| {
6198            workspace.add_item_to_active_pane(Box::new(item1), None, true, cx)
6199        });
6200        project.update(cx, |project, cx| {
6201            assert_eq!(
6202                project.active_entry(),
6203                project
6204                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6205                    .map(|e| e.id)
6206            );
6207        });
6208        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
6209
6210        // Add a second item to a non-empty pane
6211        workspace.update(cx, |workspace, cx| {
6212            workspace.add_item_to_active_pane(Box::new(item2), None, true, cx)
6213        });
6214        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
6215        project.update(cx, |project, cx| {
6216            assert_eq!(
6217                project.active_entry(),
6218                project
6219                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
6220                    .map(|e| e.id)
6221            );
6222        });
6223
6224        // Close the active item
6225        pane.update(cx, |pane, cx| {
6226            pane.close_active_item(&Default::default(), cx).unwrap()
6227        })
6228        .await
6229        .unwrap();
6230        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
6231        project.update(cx, |project, cx| {
6232            assert_eq!(
6233                project.active_entry(),
6234                project
6235                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6236                    .map(|e| e.id)
6237            );
6238        });
6239
6240        // Add a project folder
6241        project
6242            .update(cx, |project, cx| {
6243                project.find_or_create_worktree("root2", true, cx)
6244            })
6245            .await
6246            .unwrap();
6247        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
6248
6249        // Remove a project folder
6250        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
6251        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
6252    }
6253
6254    #[gpui::test]
6255    async fn test_close_window(cx: &mut TestAppContext) {
6256        init_test(cx);
6257
6258        let fs = FakeFs::new(cx.executor());
6259        fs.insert_tree("/root", json!({ "one": "" })).await;
6260
6261        let project = Project::test(fs, ["root".as_ref()], cx).await;
6262        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6263
6264        // When there are no dirty items, there's nothing to do.
6265        let item1 = cx.new_view(TestItem::new);
6266        workspace.update(cx, |w, cx| {
6267            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx)
6268        });
6269        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6270        assert!(task.await.unwrap());
6271
6272        // When there are dirty untitled items, prompt to save each one. If the user
6273        // cancels any prompt, then abort.
6274        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
6275        let item3 = cx.new_view(|cx| {
6276            TestItem::new(cx)
6277                .with_dirty(true)
6278                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6279        });
6280        workspace.update(cx, |w, cx| {
6281            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6282            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6283        });
6284        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6285        cx.executor().run_until_parked();
6286        cx.simulate_prompt_answer(2); // cancel save all
6287        cx.executor().run_until_parked();
6288        cx.simulate_prompt_answer(2); // cancel save all
6289        cx.executor().run_until_parked();
6290        assert!(!cx.has_pending_prompt());
6291        assert!(!task.await.unwrap());
6292    }
6293
6294    #[gpui::test]
6295    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
6296        init_test(cx);
6297
6298        // Register TestItem as a serializable item
6299        cx.update(|cx| {
6300            register_serializable_item::<TestItem>(cx);
6301        });
6302
6303        let fs = FakeFs::new(cx.executor());
6304        fs.insert_tree("/root", json!({ "one": "" })).await;
6305
6306        let project = Project::test(fs, ["root".as_ref()], cx).await;
6307        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6308
6309        // When there are dirty untitled items, but they can serialize, then there is no prompt.
6310        let item1 = cx.new_view(|cx| {
6311            TestItem::new(cx)
6312                .with_dirty(true)
6313                .with_serialize(|| Some(Task::ready(Ok(()))))
6314        });
6315        let item2 = cx.new_view(|cx| {
6316            TestItem::new(cx)
6317                .with_dirty(true)
6318                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6319                .with_serialize(|| Some(Task::ready(Ok(()))))
6320        });
6321        workspace.update(cx, |w, cx| {
6322            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6323            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6324        });
6325        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6326        assert!(task.await.unwrap());
6327    }
6328
6329    #[gpui::test]
6330    async fn test_close_pane_items(cx: &mut TestAppContext) {
6331        init_test(cx);
6332
6333        let fs = FakeFs::new(cx.executor());
6334
6335        let project = Project::test(fs, None, cx).await;
6336        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6337
6338        let item1 = cx.new_view(|cx| {
6339            TestItem::new(cx)
6340                .with_dirty(true)
6341                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6342        });
6343        let item2 = cx.new_view(|cx| {
6344            TestItem::new(cx)
6345                .with_dirty(true)
6346                .with_conflict(true)
6347                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
6348        });
6349        let item3 = cx.new_view(|cx| {
6350            TestItem::new(cx)
6351                .with_dirty(true)
6352                .with_conflict(true)
6353                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
6354        });
6355        let item4 = cx.new_view(|cx| {
6356            TestItem::new(cx)
6357                .with_dirty(true)
6358                .with_project_items(&[TestProjectItem::new_untitled(cx)])
6359        });
6360        let pane = workspace.update(cx, |workspace, cx| {
6361            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6362            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6363            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6364            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, cx);
6365            workspace.active_pane().clone()
6366        });
6367
6368        let close_items = pane.update(cx, |pane, cx| {
6369            pane.activate_item(1, true, true, cx);
6370            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6371            let item1_id = item1.item_id();
6372            let item3_id = item3.item_id();
6373            let item4_id = item4.item_id();
6374            pane.close_items(cx, SaveIntent::Close, move |id| {
6375                [item1_id, item3_id, item4_id].contains(&id)
6376            })
6377        });
6378        cx.executor().run_until_parked();
6379
6380        assert!(cx.has_pending_prompt());
6381        // Ignore "Save all" prompt
6382        cx.simulate_prompt_answer(2);
6383        cx.executor().run_until_parked();
6384        // There's a prompt to save item 1.
6385        pane.update(cx, |pane, _| {
6386            assert_eq!(pane.items_len(), 4);
6387            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
6388        });
6389        // Confirm saving item 1.
6390        cx.simulate_prompt_answer(0);
6391        cx.executor().run_until_parked();
6392
6393        // Item 1 is saved. There's a prompt to save item 3.
6394        pane.update(cx, |pane, cx| {
6395            assert_eq!(item1.read(cx).save_count, 1);
6396            assert_eq!(item1.read(cx).save_as_count, 0);
6397            assert_eq!(item1.read(cx).reload_count, 0);
6398            assert_eq!(pane.items_len(), 3);
6399            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
6400        });
6401        assert!(cx.has_pending_prompt());
6402
6403        // Cancel saving item 3.
6404        cx.simulate_prompt_answer(1);
6405        cx.executor().run_until_parked();
6406
6407        // Item 3 is reloaded. There's a prompt to save item 4.
6408        pane.update(cx, |pane, cx| {
6409            assert_eq!(item3.read(cx).save_count, 0);
6410            assert_eq!(item3.read(cx).save_as_count, 0);
6411            assert_eq!(item3.read(cx).reload_count, 1);
6412            assert_eq!(pane.items_len(), 2);
6413            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
6414        });
6415        assert!(cx.has_pending_prompt());
6416
6417        // Confirm saving item 4.
6418        cx.simulate_prompt_answer(0);
6419        cx.executor().run_until_parked();
6420
6421        // There's a prompt for a path for item 4.
6422        cx.simulate_new_path_selection(|_| Some(Default::default()));
6423        close_items.await.unwrap();
6424
6425        // The requested items are closed.
6426        pane.update(cx, |pane, cx| {
6427            assert_eq!(item4.read(cx).save_count, 0);
6428            assert_eq!(item4.read(cx).save_as_count, 1);
6429            assert_eq!(item4.read(cx).reload_count, 0);
6430            assert_eq!(pane.items_len(), 1);
6431            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6432        });
6433    }
6434
6435    #[gpui::test]
6436    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
6437        init_test(cx);
6438
6439        let fs = FakeFs::new(cx.executor());
6440        let project = Project::test(fs, [], cx).await;
6441        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6442
6443        // Create several workspace items with single project entries, and two
6444        // workspace items with multiple project entries.
6445        let single_entry_items = (0..=4)
6446            .map(|project_entry_id| {
6447                cx.new_view(|cx| {
6448                    TestItem::new(cx)
6449                        .with_dirty(true)
6450                        .with_project_items(&[TestProjectItem::new(
6451                            project_entry_id,
6452                            &format!("{project_entry_id}.txt"),
6453                            cx,
6454                        )])
6455                })
6456            })
6457            .collect::<Vec<_>>();
6458        let item_2_3 = cx.new_view(|cx| {
6459            TestItem::new(cx)
6460                .with_dirty(true)
6461                .with_singleton(false)
6462                .with_project_items(&[
6463                    single_entry_items[2].read(cx).project_items[0].clone(),
6464                    single_entry_items[3].read(cx).project_items[0].clone(),
6465                ])
6466        });
6467        let item_3_4 = cx.new_view(|cx| {
6468            TestItem::new(cx)
6469                .with_dirty(true)
6470                .with_singleton(false)
6471                .with_project_items(&[
6472                    single_entry_items[3].read(cx).project_items[0].clone(),
6473                    single_entry_items[4].read(cx).project_items[0].clone(),
6474                ])
6475        });
6476
6477        // Create two panes that contain the following project entries:
6478        //   left pane:
6479        //     multi-entry items:   (2, 3)
6480        //     single-entry items:  0, 1, 2, 3, 4
6481        //   right pane:
6482        //     single-entry items:  1
6483        //     multi-entry items:   (3, 4)
6484        let left_pane = workspace.update(cx, |workspace, cx| {
6485            let left_pane = workspace.active_pane().clone();
6486            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, cx);
6487            for item in single_entry_items {
6488                workspace.add_item_to_active_pane(Box::new(item), None, true, cx);
6489            }
6490            left_pane.update(cx, |pane, cx| {
6491                pane.activate_item(2, true, true, cx);
6492            });
6493
6494            let right_pane = workspace
6495                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
6496                .unwrap();
6497
6498            right_pane.update(cx, |pane, cx| {
6499                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
6500            });
6501
6502            left_pane
6503        });
6504
6505        cx.focus_view(&left_pane);
6506
6507        // When closing all of the items in the left pane, we should be prompted twice:
6508        // once for project entry 0, and once for project entry 2. Project entries 1,
6509        // 3, and 4 are all still open in the other paten. After those two
6510        // prompts, the task should complete.
6511
6512        let close = left_pane.update(cx, |pane, cx| {
6513            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
6514        });
6515        cx.executor().run_until_parked();
6516
6517        // Discard "Save all" prompt
6518        cx.simulate_prompt_answer(2);
6519
6520        cx.executor().run_until_parked();
6521        left_pane.update(cx, |pane, cx| {
6522            assert_eq!(
6523                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6524                &[ProjectEntryId::from_proto(0)]
6525            );
6526        });
6527        cx.simulate_prompt_answer(0);
6528
6529        cx.executor().run_until_parked();
6530        left_pane.update(cx, |pane, cx| {
6531            assert_eq!(
6532                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6533                &[ProjectEntryId::from_proto(2)]
6534            );
6535        });
6536        cx.simulate_prompt_answer(0);
6537
6538        cx.executor().run_until_parked();
6539        close.await.unwrap();
6540        left_pane.update(cx, |pane, _| {
6541            assert_eq!(pane.items_len(), 0);
6542        });
6543    }
6544
6545    #[gpui::test]
6546    async fn test_autosave(cx: &mut gpui::TestAppContext) {
6547        init_test(cx);
6548
6549        let fs = FakeFs::new(cx.executor());
6550        let project = Project::test(fs, [], cx).await;
6551        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6552        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6553
6554        let item = cx.new_view(|cx| {
6555            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6556        });
6557        let item_id = item.entity_id();
6558        workspace.update(cx, |workspace, cx| {
6559            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6560        });
6561
6562        // Autosave on window change.
6563        item.update(cx, |item, cx| {
6564            SettingsStore::update_global(cx, |settings, cx| {
6565                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6566                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
6567                })
6568            });
6569            item.is_dirty = true;
6570        });
6571
6572        // Deactivating the window saves the file.
6573        cx.deactivate_window();
6574        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6575
6576        // Re-activating the window doesn't save the file.
6577        cx.update(|cx| cx.activate_window());
6578        cx.executor().run_until_parked();
6579        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6580
6581        // Autosave on focus change.
6582        item.update(cx, |item, cx| {
6583            cx.focus_self();
6584            SettingsStore::update_global(cx, |settings, cx| {
6585                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6586                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6587                })
6588            });
6589            item.is_dirty = true;
6590        });
6591
6592        // Blurring the item saves the file.
6593        item.update(cx, |_, cx| cx.blur());
6594        cx.executor().run_until_parked();
6595        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
6596
6597        // Deactivating the window still saves the file.
6598        item.update(cx, |item, cx| {
6599            cx.focus_self();
6600            item.is_dirty = true;
6601        });
6602        cx.deactivate_window();
6603        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6604
6605        // Autosave after delay.
6606        item.update(cx, |item, cx| {
6607            SettingsStore::update_global(cx, |settings, cx| {
6608                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6609                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
6610                })
6611            });
6612            item.is_dirty = true;
6613            cx.emit(ItemEvent::Edit);
6614        });
6615
6616        // Delay hasn't fully expired, so the file is still dirty and unsaved.
6617        cx.executor().advance_clock(Duration::from_millis(250));
6618        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6619
6620        // After delay expires, the file is saved.
6621        cx.executor().advance_clock(Duration::from_millis(250));
6622        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
6623
6624        // Autosave on focus change, ensuring closing the tab counts as such.
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::OnFocusChange);
6629                })
6630            });
6631            item.is_dirty = true;
6632        });
6633
6634        pane.update(cx, |pane, cx| {
6635            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6636        })
6637        .await
6638        .unwrap();
6639        assert!(!cx.has_pending_prompt());
6640        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6641
6642        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
6643        workspace.update(cx, |workspace, cx| {
6644            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6645        });
6646        item.update(cx, |item, cx| {
6647            item.project_items[0].update(cx, |item, _| {
6648                item.entry_id = None;
6649            });
6650            item.is_dirty = true;
6651            cx.blur();
6652        });
6653        cx.run_until_parked();
6654        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6655
6656        // Ensure autosave is prevented for deleted files also when closing the buffer.
6657        let _close_items = pane.update(cx, |pane, cx| {
6658            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6659        });
6660        cx.run_until_parked();
6661        assert!(cx.has_pending_prompt());
6662        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6663    }
6664
6665    #[gpui::test]
6666    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
6667        init_test(cx);
6668
6669        let fs = FakeFs::new(cx.executor());
6670
6671        let project = Project::test(fs, [], cx).await;
6672        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6673
6674        let item = cx.new_view(|cx| {
6675            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6676        });
6677        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6678        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
6679        let toolbar_notify_count = Rc::new(RefCell::new(0));
6680
6681        workspace.update(cx, |workspace, cx| {
6682            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6683            let toolbar_notification_count = toolbar_notify_count.clone();
6684            cx.observe(&toolbar, move |_, _, _| {
6685                *toolbar_notification_count.borrow_mut() += 1
6686            })
6687            .detach();
6688        });
6689
6690        pane.update(cx, |pane, _| {
6691            assert!(!pane.can_navigate_backward());
6692            assert!(!pane.can_navigate_forward());
6693        });
6694
6695        item.update(cx, |item, cx| {
6696            item.set_state("one".to_string(), cx);
6697        });
6698
6699        // Toolbar must be notified to re-render the navigation buttons
6700        assert_eq!(*toolbar_notify_count.borrow(), 1);
6701
6702        pane.update(cx, |pane, _| {
6703            assert!(pane.can_navigate_backward());
6704            assert!(!pane.can_navigate_forward());
6705        });
6706
6707        workspace
6708            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
6709            .await
6710            .unwrap();
6711
6712        assert_eq!(*toolbar_notify_count.borrow(), 2);
6713        pane.update(cx, |pane, _| {
6714            assert!(!pane.can_navigate_backward());
6715            assert!(pane.can_navigate_forward());
6716        });
6717    }
6718
6719    #[gpui::test]
6720    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
6721        init_test(cx);
6722        let fs = FakeFs::new(cx.executor());
6723
6724        let project = Project::test(fs, [], cx).await;
6725        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6726
6727        let panel = workspace.update(cx, |workspace, cx| {
6728            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6729            workspace.add_panel(panel.clone(), cx);
6730
6731            workspace
6732                .right_dock()
6733                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6734
6735            panel
6736        });
6737
6738        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6739        pane.update(cx, |pane, cx| {
6740            let item = cx.new_view(TestItem::new);
6741            pane.add_item(Box::new(item), true, true, None, cx);
6742        });
6743
6744        // Transfer focus from center to panel
6745        workspace.update(cx, |workspace, cx| {
6746            workspace.toggle_panel_focus::<TestPanel>(cx);
6747        });
6748
6749        workspace.update(cx, |workspace, cx| {
6750            assert!(workspace.right_dock().read(cx).is_open());
6751            assert!(!panel.is_zoomed(cx));
6752            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6753        });
6754
6755        // Transfer focus from panel to center
6756        workspace.update(cx, |workspace, cx| {
6757            workspace.toggle_panel_focus::<TestPanel>(cx);
6758        });
6759
6760        workspace.update(cx, |workspace, cx| {
6761            assert!(workspace.right_dock().read(cx).is_open());
6762            assert!(!panel.is_zoomed(cx));
6763            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6764        });
6765
6766        // Close the dock
6767        workspace.update(cx, |workspace, cx| {
6768            workspace.toggle_dock(DockPosition::Right, cx);
6769        });
6770
6771        workspace.update(cx, |workspace, cx| {
6772            assert!(!workspace.right_dock().read(cx).is_open());
6773            assert!(!panel.is_zoomed(cx));
6774            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6775        });
6776
6777        // Open the dock
6778        workspace.update(cx, |workspace, cx| {
6779            workspace.toggle_dock(DockPosition::Right, cx);
6780        });
6781
6782        workspace.update(cx, |workspace, cx| {
6783            assert!(workspace.right_dock().read(cx).is_open());
6784            assert!(!panel.is_zoomed(cx));
6785            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6786        });
6787
6788        // Focus and zoom panel
6789        panel.update(cx, |panel, cx| {
6790            cx.focus_self();
6791            panel.set_zoomed(true, cx)
6792        });
6793
6794        workspace.update(cx, |workspace, cx| {
6795            assert!(workspace.right_dock().read(cx).is_open());
6796            assert!(panel.is_zoomed(cx));
6797            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6798        });
6799
6800        // Transfer focus to the center closes the dock
6801        workspace.update(cx, |workspace, cx| {
6802            workspace.toggle_panel_focus::<TestPanel>(cx);
6803        });
6804
6805        workspace.update(cx, |workspace, cx| {
6806            assert!(!workspace.right_dock().read(cx).is_open());
6807            assert!(panel.is_zoomed(cx));
6808            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6809        });
6810
6811        // Transferring focus back to the panel keeps it zoomed
6812        workspace.update(cx, |workspace, cx| {
6813            workspace.toggle_panel_focus::<TestPanel>(cx);
6814        });
6815
6816        workspace.update(cx, |workspace, cx| {
6817            assert!(workspace.right_dock().read(cx).is_open());
6818            assert!(panel.is_zoomed(cx));
6819            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6820        });
6821
6822        // Close the dock while it is zoomed
6823        workspace.update(cx, |workspace, cx| {
6824            workspace.toggle_dock(DockPosition::Right, cx)
6825        });
6826
6827        workspace.update(cx, |workspace, cx| {
6828            assert!(!workspace.right_dock().read(cx).is_open());
6829            assert!(panel.is_zoomed(cx));
6830            assert!(workspace.zoomed.is_none());
6831            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6832        });
6833
6834        // Opening the dock, when it's zoomed, retains focus
6835        workspace.update(cx, |workspace, cx| {
6836            workspace.toggle_dock(DockPosition::Right, cx)
6837        });
6838
6839        workspace.update(cx, |workspace, cx| {
6840            assert!(workspace.right_dock().read(cx).is_open());
6841            assert!(panel.is_zoomed(cx));
6842            assert!(workspace.zoomed.is_some());
6843            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6844        });
6845
6846        // Unzoom and close the panel, zoom the active pane.
6847        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
6848        workspace.update(cx, |workspace, cx| {
6849            workspace.toggle_dock(DockPosition::Right, cx)
6850        });
6851        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
6852
6853        // Opening a dock unzooms the pane.
6854        workspace.update(cx, |workspace, cx| {
6855            workspace.toggle_dock(DockPosition::Right, cx)
6856        });
6857        workspace.update(cx, |workspace, cx| {
6858            let pane = pane.read(cx);
6859            assert!(!pane.is_zoomed());
6860            assert!(!pane.focus_handle(cx).is_focused(cx));
6861            assert!(workspace.right_dock().read(cx).is_open());
6862            assert!(workspace.zoomed.is_none());
6863        });
6864    }
6865
6866    #[gpui::test]
6867    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
6868        init_test(cx);
6869
6870        let fs = FakeFs::new(cx.executor());
6871
6872        let project = Project::test(fs, None, cx).await;
6873        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6874
6875        // Let's arrange the panes like this:
6876        //
6877        // +-----------------------+
6878        // |         top           |
6879        // +------+--------+-------+
6880        // | left | center | right |
6881        // +------+--------+-------+
6882        // |        bottom         |
6883        // +-----------------------+
6884
6885        let top_item = cx.new_view(|cx| {
6886            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
6887        });
6888        let bottom_item = cx.new_view(|cx| {
6889            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
6890        });
6891        let left_item = cx.new_view(|cx| {
6892            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
6893        });
6894        let right_item = cx.new_view(|cx| {
6895            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
6896        });
6897        let center_item = cx.new_view(|cx| {
6898            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
6899        });
6900
6901        let top_pane_id = workspace.update(cx, |workspace, cx| {
6902            let top_pane_id = workspace.active_pane().entity_id();
6903            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, cx);
6904            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Down, cx);
6905            top_pane_id
6906        });
6907        let bottom_pane_id = workspace.update(cx, |workspace, cx| {
6908            let bottom_pane_id = workspace.active_pane().entity_id();
6909            workspace.add_item_to_active_pane(Box::new(bottom_item.clone()), None, false, cx);
6910            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Up, cx);
6911            bottom_pane_id
6912        });
6913        let left_pane_id = workspace.update(cx, |workspace, cx| {
6914            let left_pane_id = workspace.active_pane().entity_id();
6915            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, cx);
6916            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
6917            left_pane_id
6918        });
6919        let right_pane_id = workspace.update(cx, |workspace, cx| {
6920            let right_pane_id = workspace.active_pane().entity_id();
6921            workspace.add_item_to_active_pane(Box::new(right_item.clone()), None, false, cx);
6922            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Left, cx);
6923            right_pane_id
6924        });
6925        let center_pane_id = workspace.update(cx, |workspace, cx| {
6926            let center_pane_id = workspace.active_pane().entity_id();
6927            workspace.add_item_to_active_pane(Box::new(center_item.clone()), None, false, cx);
6928            center_pane_id
6929        });
6930        cx.executor().run_until_parked();
6931
6932        workspace.update(cx, |workspace, cx| {
6933            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
6934
6935            // Join into next from center pane into right
6936            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6937        });
6938
6939        workspace.update(cx, |workspace, cx| {
6940            let active_pane = workspace.active_pane();
6941            assert_eq!(right_pane_id, active_pane.entity_id());
6942            assert_eq!(2, active_pane.read(cx).items_len());
6943            let item_ids_in_pane =
6944                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6945            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6946            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6947
6948            // Join into next from right pane into bottom
6949            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6950        });
6951
6952        workspace.update(cx, |workspace, cx| {
6953            let active_pane = workspace.active_pane();
6954            assert_eq!(bottom_pane_id, active_pane.entity_id());
6955            assert_eq!(3, active_pane.read(cx).items_len());
6956            let item_ids_in_pane =
6957                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6958            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6959            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6960            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
6961
6962            // Join into next from bottom pane into left
6963            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6964        });
6965
6966        workspace.update(cx, |workspace, cx| {
6967            let active_pane = workspace.active_pane();
6968            assert_eq!(left_pane_id, active_pane.entity_id());
6969            assert_eq!(4, active_pane.read(cx).items_len());
6970            let item_ids_in_pane =
6971                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6972            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6973            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6974            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
6975            assert!(item_ids_in_pane.contains(&left_item.item_id()));
6976
6977            // Join into next from left pane into top
6978            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6979        });
6980
6981        workspace.update(cx, |workspace, cx| {
6982            let active_pane = workspace.active_pane();
6983            assert_eq!(top_pane_id, active_pane.entity_id());
6984            assert_eq!(5, active_pane.read(cx).items_len());
6985            let item_ids_in_pane =
6986                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6987            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6988            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6989            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
6990            assert!(item_ids_in_pane.contains(&left_item.item_id()));
6991            assert!(item_ids_in_pane.contains(&top_item.item_id()));
6992
6993            // Single pane left: no-op
6994            workspace.join_pane_into_next(workspace.active_pane().clone(), cx)
6995        });
6996
6997        workspace.update(cx, |workspace, _cx| {
6998            let active_pane = workspace.active_pane();
6999            assert_eq!(top_pane_id, active_pane.entity_id());
7000        });
7001    }
7002
7003    fn add_an_item_to_active_pane(
7004        cx: &mut VisualTestContext,
7005        workspace: &View<Workspace>,
7006        item_id: u64,
7007    ) -> View<TestItem> {
7008        let item = cx.new_view(|cx| {
7009            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
7010                item_id,
7011                "item{item_id}.txt",
7012                cx,
7013            )])
7014        });
7015        workspace.update(cx, |workspace, cx| {
7016            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, cx);
7017        });
7018        return item;
7019    }
7020
7021    fn split_pane(cx: &mut VisualTestContext, workspace: &View<Workspace>) -> View<Pane> {
7022        return workspace.update(cx, |workspace, cx| {
7023            let new_pane =
7024                workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
7025            new_pane
7026        });
7027    }
7028
7029    #[gpui::test]
7030    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
7031        init_test(cx);
7032        let fs = FakeFs::new(cx.executor());
7033        let project = Project::test(fs, None, cx).await;
7034        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
7035
7036        add_an_item_to_active_pane(cx, &workspace, 1);
7037        split_pane(cx, &workspace);
7038        add_an_item_to_active_pane(cx, &workspace, 2);
7039        split_pane(cx, &workspace); // empty pane
7040        split_pane(cx, &workspace);
7041        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
7042
7043        cx.executor().run_until_parked();
7044
7045        workspace.update(cx, |workspace, cx| {
7046            let num_panes = workspace.panes().len();
7047            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
7048            let active_item = workspace
7049                .active_pane()
7050                .read(cx)
7051                .active_item()
7052                .expect("item is in focus");
7053
7054            assert_eq!(num_panes, 4);
7055            assert_eq!(num_items_in_current_pane, 1);
7056            assert_eq!(active_item.item_id(), last_item.item_id());
7057        });
7058
7059        workspace.update(cx, |workspace, cx| {
7060            workspace.join_all_panes(cx);
7061        });
7062
7063        workspace.update(cx, |workspace, cx| {
7064            let num_panes = workspace.panes().len();
7065            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
7066            let active_item = workspace
7067                .active_pane()
7068                .read(cx)
7069                .active_item()
7070                .expect("item is in focus");
7071
7072            assert_eq!(num_panes, 1);
7073            assert_eq!(num_items_in_current_pane, 3);
7074            assert_eq!(active_item.item_id(), last_item.item_id());
7075        });
7076    }
7077    struct TestModal(FocusHandle);
7078
7079    impl TestModal {
7080        fn new(cx: &mut ViewContext<Self>) -> Self {
7081            Self(cx.focus_handle())
7082        }
7083    }
7084
7085    impl EventEmitter<DismissEvent> for TestModal {}
7086
7087    impl FocusableView for TestModal {
7088        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7089            self.0.clone()
7090        }
7091    }
7092
7093    impl ModalView for TestModal {}
7094
7095    impl Render for TestModal {
7096        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
7097            div().track_focus(&self.0)
7098        }
7099    }
7100
7101    #[gpui::test]
7102    async fn test_panels(cx: &mut gpui::TestAppContext) {
7103        init_test(cx);
7104        let fs = FakeFs::new(cx.executor());
7105
7106        let project = Project::test(fs, [], cx).await;
7107        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
7108
7109        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
7110            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
7111            workspace.add_panel(panel_1.clone(), cx);
7112            workspace
7113                .left_dock()
7114                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
7115            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
7116            workspace.add_panel(panel_2.clone(), cx);
7117            workspace
7118                .right_dock()
7119                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
7120
7121            let left_dock = workspace.left_dock();
7122            assert_eq!(
7123                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7124                panel_1.panel_id()
7125            );
7126            assert_eq!(
7127                left_dock.read(cx).active_panel_size(cx).unwrap(),
7128                panel_1.size(cx)
7129            );
7130
7131            left_dock.update(cx, |left_dock, cx| {
7132                left_dock.resize_active_panel(Some(px(1337.)), cx)
7133            });
7134            assert_eq!(
7135                workspace
7136                    .right_dock()
7137                    .read(cx)
7138                    .visible_panel()
7139                    .unwrap()
7140                    .panel_id(),
7141                panel_2.panel_id(),
7142            );
7143
7144            (panel_1, panel_2)
7145        });
7146
7147        // Move panel_1 to the right
7148        panel_1.update(cx, |panel_1, cx| {
7149            panel_1.set_position(DockPosition::Right, cx)
7150        });
7151
7152        workspace.update(cx, |workspace, cx| {
7153            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
7154            // Since it was the only panel on the left, the left dock should now be closed.
7155            assert!(!workspace.left_dock().read(cx).is_open());
7156            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
7157            let right_dock = workspace.right_dock();
7158            assert_eq!(
7159                right_dock.read(cx).visible_panel().unwrap().panel_id(),
7160                panel_1.panel_id()
7161            );
7162            assert_eq!(
7163                right_dock.read(cx).active_panel_size(cx).unwrap(),
7164                px(1337.)
7165            );
7166
7167            // Now we move panel_2 to the left
7168            panel_2.set_position(DockPosition::Left, cx);
7169        });
7170
7171        workspace.update(cx, |workspace, cx| {
7172            // Since panel_2 was not visible on the right, we don't open the left dock.
7173            assert!(!workspace.left_dock().read(cx).is_open());
7174            // And the right dock is unaffected in its displaying of panel_1
7175            assert!(workspace.right_dock().read(cx).is_open());
7176            assert_eq!(
7177                workspace
7178                    .right_dock()
7179                    .read(cx)
7180                    .visible_panel()
7181                    .unwrap()
7182                    .panel_id(),
7183                panel_1.panel_id(),
7184            );
7185        });
7186
7187        // Move panel_1 back to the left
7188        panel_1.update(cx, |panel_1, cx| {
7189            panel_1.set_position(DockPosition::Left, cx)
7190        });
7191
7192        workspace.update(cx, |workspace, cx| {
7193            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
7194            let left_dock = workspace.left_dock();
7195            assert!(left_dock.read(cx).is_open());
7196            assert_eq!(
7197                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7198                panel_1.panel_id()
7199            );
7200            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
7201            // And the right dock should be closed as it no longer has any panels.
7202            assert!(!workspace.right_dock().read(cx).is_open());
7203
7204            // Now we move panel_1 to the bottom
7205            panel_1.set_position(DockPosition::Bottom, cx);
7206        });
7207
7208        workspace.update(cx, |workspace, cx| {
7209            // Since panel_1 was visible on the left, we close the left dock.
7210            assert!(!workspace.left_dock().read(cx).is_open());
7211            // The bottom dock is sized based on the panel's default size,
7212            // since the panel orientation changed from vertical to horizontal.
7213            let bottom_dock = workspace.bottom_dock();
7214            assert_eq!(
7215                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
7216                panel_1.size(cx),
7217            );
7218            // Close bottom dock and move panel_1 back to the left.
7219            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
7220            panel_1.set_position(DockPosition::Left, cx);
7221        });
7222
7223        // Emit activated event on panel 1
7224        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
7225
7226        // Now the left dock is open and panel_1 is active and focused.
7227        workspace.update(cx, |workspace, cx| {
7228            let left_dock = workspace.left_dock();
7229            assert!(left_dock.read(cx).is_open());
7230            assert_eq!(
7231                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7232                panel_1.panel_id(),
7233            );
7234            assert!(panel_1.focus_handle(cx).is_focused(cx));
7235        });
7236
7237        // Emit closed event on panel 2, which is not active
7238        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7239
7240        // Wo don't close the left dock, because panel_2 wasn't the active panel
7241        workspace.update(cx, |workspace, cx| {
7242            let left_dock = workspace.left_dock();
7243            assert!(left_dock.read(cx).is_open());
7244            assert_eq!(
7245                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7246                panel_1.panel_id(),
7247            );
7248        });
7249
7250        // Emitting a ZoomIn event shows the panel as zoomed.
7251        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
7252        workspace.update(cx, |workspace, _| {
7253            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7254            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
7255        });
7256
7257        // Move panel to another dock while it is zoomed
7258        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
7259        workspace.update(cx, |workspace, _| {
7260            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7261
7262            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7263        });
7264
7265        // This is a helper for getting a:
7266        // - valid focus on an element,
7267        // - that isn't a part of the panes and panels system of the Workspace,
7268        // - and doesn't trigger the 'on_focus_lost' API.
7269        let focus_other_view = {
7270            let workspace = workspace.clone();
7271            move |cx: &mut VisualTestContext| {
7272                workspace.update(cx, |workspace, cx| {
7273                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
7274                        workspace.toggle_modal(cx, TestModal::new);
7275                        workspace.toggle_modal(cx, TestModal::new);
7276                    } else {
7277                        workspace.toggle_modal(cx, TestModal::new);
7278                    }
7279                })
7280            }
7281        };
7282
7283        // If focus is transferred to another view that's not a panel or another pane, we still show
7284        // the panel as zoomed.
7285        focus_other_view(cx);
7286        workspace.update(cx, |workspace, _| {
7287            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7288            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7289        });
7290
7291        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
7292        workspace.update(cx, |_, cx| cx.focus_self());
7293        workspace.update(cx, |workspace, _| {
7294            assert_eq!(workspace.zoomed, None);
7295            assert_eq!(workspace.zoomed_position, None);
7296        });
7297
7298        // If focus is transferred again to another view that's not a panel or a pane, we won't
7299        // show the panel as zoomed because it wasn't zoomed before.
7300        focus_other_view(cx);
7301        workspace.update(cx, |workspace, _| {
7302            assert_eq!(workspace.zoomed, None);
7303            assert_eq!(workspace.zoomed_position, None);
7304        });
7305
7306        // When the panel is activated, it is zoomed again.
7307        cx.dispatch_action(ToggleRightDock);
7308        workspace.update(cx, |workspace, _| {
7309            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7310            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7311        });
7312
7313        // Emitting a ZoomOut event unzooms the panel.
7314        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
7315        workspace.update(cx, |workspace, _| {
7316            assert_eq!(workspace.zoomed, None);
7317            assert_eq!(workspace.zoomed_position, None);
7318        });
7319
7320        // Emit closed event on panel 1, which is active
7321        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7322
7323        // Now the left dock is closed, because panel_1 was the active panel
7324        workspace.update(cx, |workspace, cx| {
7325            let right_dock = workspace.right_dock();
7326            assert!(!right_dock.read(cx).is_open());
7327        });
7328    }
7329
7330    mod register_project_item_tests {
7331        use ui::Context as _;
7332
7333        use super::*;
7334
7335        // View
7336        struct TestPngItemView {
7337            focus_handle: FocusHandle,
7338        }
7339        // Model
7340        struct TestPngItem {}
7341
7342        impl project::Item for TestPngItem {
7343            fn try_open(
7344                _project: &Model<Project>,
7345                path: &ProjectPath,
7346                cx: &mut AppContext,
7347            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7348                if path.path.extension().unwrap() == "png" {
7349                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
7350                } else {
7351                    None
7352                }
7353            }
7354
7355            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7356                None
7357            }
7358
7359            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7360                None
7361            }
7362        }
7363
7364        impl Item for TestPngItemView {
7365            type Event = ();
7366        }
7367        impl EventEmitter<()> for TestPngItemView {}
7368        impl FocusableView for TestPngItemView {
7369            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7370                self.focus_handle.clone()
7371            }
7372        }
7373
7374        impl Render for TestPngItemView {
7375            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7376                Empty
7377            }
7378        }
7379
7380        impl ProjectItem for TestPngItemView {
7381            type Item = TestPngItem;
7382
7383            fn for_project_item(
7384                _project: Model<Project>,
7385                _item: Model<Self::Item>,
7386                cx: &mut ViewContext<Self>,
7387            ) -> Self
7388            where
7389                Self: Sized,
7390            {
7391                Self {
7392                    focus_handle: cx.focus_handle(),
7393                }
7394            }
7395        }
7396
7397        // View
7398        struct TestIpynbItemView {
7399            focus_handle: FocusHandle,
7400        }
7401        // Model
7402        struct TestIpynbItem {}
7403
7404        impl project::Item for TestIpynbItem {
7405            fn try_open(
7406                _project: &Model<Project>,
7407                path: &ProjectPath,
7408                cx: &mut AppContext,
7409            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7410                if path.path.extension().unwrap() == "ipynb" {
7411                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
7412                } else {
7413                    None
7414                }
7415            }
7416
7417            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7418                None
7419            }
7420
7421            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7422                None
7423            }
7424        }
7425
7426        impl Item for TestIpynbItemView {
7427            type Event = ();
7428        }
7429        impl EventEmitter<()> for TestIpynbItemView {}
7430        impl FocusableView for TestIpynbItemView {
7431            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7432                self.focus_handle.clone()
7433            }
7434        }
7435
7436        impl Render for TestIpynbItemView {
7437            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7438                Empty
7439            }
7440        }
7441
7442        impl ProjectItem for TestIpynbItemView {
7443            type Item = TestIpynbItem;
7444
7445            fn for_project_item(
7446                _project: Model<Project>,
7447                _item: Model<Self::Item>,
7448                cx: &mut ViewContext<Self>,
7449            ) -> Self
7450            where
7451                Self: Sized,
7452            {
7453                Self {
7454                    focus_handle: cx.focus_handle(),
7455                }
7456            }
7457        }
7458
7459        struct TestAlternatePngItemView {
7460            focus_handle: FocusHandle,
7461        }
7462
7463        impl Item for TestAlternatePngItemView {
7464            type Event = ();
7465        }
7466
7467        impl EventEmitter<()> for TestAlternatePngItemView {}
7468        impl FocusableView for TestAlternatePngItemView {
7469            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7470                self.focus_handle.clone()
7471            }
7472        }
7473
7474        impl Render for TestAlternatePngItemView {
7475            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7476                Empty
7477            }
7478        }
7479
7480        impl ProjectItem for TestAlternatePngItemView {
7481            type Item = TestPngItem;
7482
7483            fn for_project_item(
7484                _project: Model<Project>,
7485                _item: Model<Self::Item>,
7486                cx: &mut ViewContext<Self>,
7487            ) -> Self
7488            where
7489                Self: Sized,
7490            {
7491                Self {
7492                    focus_handle: cx.focus_handle(),
7493                }
7494            }
7495        }
7496
7497        #[gpui::test]
7498        async fn test_register_project_item(cx: &mut TestAppContext) {
7499            init_test(cx);
7500
7501            cx.update(|cx| {
7502                register_project_item::<TestPngItemView>(cx);
7503                register_project_item::<TestIpynbItemView>(cx);
7504            });
7505
7506            let fs = FakeFs::new(cx.executor());
7507            fs.insert_tree(
7508                "/root1",
7509                json!({
7510                    "one.png": "BINARYDATAHERE",
7511                    "two.ipynb": "{ totally a notebook }",
7512                    "three.txt": "editing text, sure why not?"
7513                }),
7514            )
7515            .await;
7516
7517            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7518            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7519
7520            let worktree_id = project.update(cx, |project, cx| {
7521                project.worktrees(cx).next().unwrap().read(cx).id()
7522            });
7523
7524            let handle = workspace
7525                .update(cx, |workspace, cx| {
7526                    let project_path = (worktree_id, "one.png");
7527                    workspace.open_path(project_path, None, true, cx)
7528                })
7529                .await
7530                .unwrap();
7531
7532            // Now we can check if the handle we got back errored or not
7533            assert_eq!(
7534                handle.to_any().entity_type(),
7535                TypeId::of::<TestPngItemView>()
7536            );
7537
7538            let handle = workspace
7539                .update(cx, |workspace, cx| {
7540                    let project_path = (worktree_id, "two.ipynb");
7541                    workspace.open_path(project_path, None, true, cx)
7542                })
7543                .await
7544                .unwrap();
7545
7546            assert_eq!(
7547                handle.to_any().entity_type(),
7548                TypeId::of::<TestIpynbItemView>()
7549            );
7550
7551            let handle = workspace
7552                .update(cx, |workspace, cx| {
7553                    let project_path = (worktree_id, "three.txt");
7554                    workspace.open_path(project_path, None, true, cx)
7555                })
7556                .await;
7557            assert!(handle.is_err());
7558        }
7559
7560        #[gpui::test]
7561        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
7562            init_test(cx);
7563
7564            cx.update(|cx| {
7565                register_project_item::<TestPngItemView>(cx);
7566                register_project_item::<TestAlternatePngItemView>(cx);
7567            });
7568
7569            let fs = FakeFs::new(cx.executor());
7570            fs.insert_tree(
7571                "/root1",
7572                json!({
7573                    "one.png": "BINARYDATAHERE",
7574                    "two.ipynb": "{ totally a notebook }",
7575                    "three.txt": "editing text, sure why not?"
7576                }),
7577            )
7578            .await;
7579
7580            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7581            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7582
7583            let worktree_id = project.update(cx, |project, cx| {
7584                project.worktrees(cx).next().unwrap().read(cx).id()
7585            });
7586
7587            let handle = workspace
7588                .update(cx, |workspace, cx| {
7589                    let project_path = (worktree_id, "one.png");
7590                    workspace.open_path(project_path, None, true, cx)
7591                })
7592                .await
7593                .unwrap();
7594
7595            // This _must_ be the second item registered
7596            assert_eq!(
7597                handle.to_any().entity_type(),
7598                TypeId::of::<TestAlternatePngItemView>()
7599            );
7600
7601            let handle = workspace
7602                .update(cx, |workspace, cx| {
7603                    let project_path = (worktree_id, "three.txt");
7604                    workspace.open_path(project_path, None, true, cx)
7605                })
7606                .await;
7607            assert!(handle.is_err());
7608        }
7609    }
7610
7611    pub fn init_test(cx: &mut TestAppContext) {
7612        cx.update(|cx| {
7613            let settings_store = SettingsStore::test(cx);
7614            cx.set_global(settings_store);
7615            theme::init(theme::LoadThemes::JustBase, cx);
7616            language::init(cx);
7617            crate::init_settings(cx);
7618            Project::init_settings(cx);
7619        });
7620    }
7621}