workspace.rs

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