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