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                workspace.set_serialized_ssh_project(serialized_ssh_project);
5579                workspace
5580            });
5581        })?;
5582
5583        window
5584            .update(&mut cx, |_, cx| {
5585                cx.activate_window();
5586
5587                open_items(serialized_workspace, project_paths_to_open, app_state, cx)
5588            })?
5589            .await?;
5590
5591        window.update(&mut cx, |workspace, cx| {
5592            for error in project_path_errors {
5593                if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
5594                    if let Some(path) = error.error_tag("path") {
5595                        workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
5596                    }
5597                } else {
5598                    workspace.show_error(&error, cx)
5599                }
5600            }
5601        })
5602    })
5603}
5604
5605fn serialize_ssh_project(
5606    connection_options: SshConnectionOptions,
5607    paths: Vec<PathBuf>,
5608    cx: &AsyncAppContext,
5609) -> Task<
5610    Result<(
5611        SerializedSshProject,
5612        WorkspaceId,
5613        Option<SerializedWorkspace>,
5614    )>,
5615> {
5616    cx.background_executor().spawn(async move {
5617        let serialized_ssh_project = persistence::DB
5618            .get_or_create_ssh_project(
5619                connection_options.host.clone(),
5620                connection_options.port,
5621                paths
5622                    .iter()
5623                    .map(|path| path.to_string_lossy().to_string())
5624                    .collect::<Vec<_>>(),
5625                connection_options.username.clone(),
5626            )
5627            .await?;
5628
5629        let serialized_workspace =
5630            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
5631
5632        let workspace_id = if let Some(workspace_id) =
5633            serialized_workspace.as_ref().map(|workspace| workspace.id)
5634        {
5635            workspace_id
5636        } else {
5637            persistence::DB.next_id().await?
5638        };
5639
5640        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
5641    })
5642}
5643
5644pub fn join_dev_server_project(
5645    dev_server_project_id: DevServerProjectId,
5646    project_id: ProjectId,
5647    app_state: Arc<AppState>,
5648    window_to_replace: Option<WindowHandle<Workspace>>,
5649    cx: &mut AppContext,
5650) -> Task<Result<WindowHandle<Workspace>>> {
5651    let windows = cx.windows();
5652    cx.spawn(|mut cx| async move {
5653        let existing_workspace = windows.into_iter().find_map(|window| {
5654            window.downcast::<Workspace>().and_then(|window| {
5655                window
5656                    .update(&mut cx, |workspace, cx| {
5657                        if workspace.project().read(cx).remote_id() == Some(project_id.0) {
5658                            Some(window)
5659                        } else {
5660                            None
5661                        }
5662                    })
5663                    .unwrap_or(None)
5664            })
5665        });
5666
5667        let serialized_workspace: Option<SerializedWorkspace> =
5668            persistence::DB.workspace_for_dev_server_project(dev_server_project_id);
5669
5670        let workspace = if let Some(existing_workspace) = existing_workspace {
5671            existing_workspace
5672        } else {
5673            let project = Project::remote(
5674                project_id.0,
5675                app_state.client.clone(),
5676                app_state.user_store.clone(),
5677                app_state.languages.clone(),
5678                app_state.fs.clone(),
5679                cx.clone(),
5680            )
5681            .await?;
5682
5683            let workspace_id = if let Some(ref serialized_workspace) = serialized_workspace {
5684                serialized_workspace.id
5685            } else {
5686                persistence::DB.next_id().await?
5687            };
5688
5689            if let Some(window_to_replace) = window_to_replace {
5690                cx.update_window(window_to_replace.into(), |_, cx| {
5691                    cx.replace_root_view(|cx| {
5692                        Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5693                    });
5694                })?;
5695                window_to_replace
5696            } else {
5697                let window_bounds_override = window_bounds_env_override();
5698                cx.update(|cx| {
5699                    let mut options = (app_state.build_window_options)(None, cx);
5700                    options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
5701                    cx.open_window(options, |cx| {
5702                        cx.new_view(|cx| {
5703                            Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5704                        })
5705                    })
5706                })??
5707            }
5708        };
5709
5710        workspace
5711            .update(&mut cx, |_, cx| {
5712                cx.activate(true);
5713                cx.activate_window();
5714                open_items(serialized_workspace, vec![], app_state, cx)
5715            })?
5716            .await?;
5717
5718        anyhow::Ok(workspace)
5719    })
5720}
5721
5722pub fn join_in_room_project(
5723    project_id: u64,
5724    follow_user_id: u64,
5725    app_state: Arc<AppState>,
5726    cx: &mut AppContext,
5727) -> Task<Result<()>> {
5728    let windows = cx.windows();
5729    cx.spawn(|mut cx| async move {
5730        let existing_workspace = windows.into_iter().find_map(|window| {
5731            window.downcast::<Workspace>().and_then(|window| {
5732                window
5733                    .update(&mut cx, |workspace, cx| {
5734                        if workspace.project().read(cx).remote_id() == Some(project_id) {
5735                            Some(window)
5736                        } else {
5737                            None
5738                        }
5739                    })
5740                    .unwrap_or(None)
5741            })
5742        });
5743
5744        let workspace = if let Some(existing_workspace) = existing_workspace {
5745            existing_workspace
5746        } else {
5747            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
5748            let room = active_call
5749                .read_with(&cx, |call, _| call.room().cloned())?
5750                .ok_or_else(|| anyhow!("not in a call"))?;
5751            let project = room
5752                .update(&mut cx, |room, cx| {
5753                    room.join_project(
5754                        project_id,
5755                        app_state.languages.clone(),
5756                        app_state.fs.clone(),
5757                        cx,
5758                    )
5759                })?
5760                .await?;
5761
5762            let window_bounds_override = window_bounds_env_override();
5763            cx.update(|cx| {
5764                let mut options = (app_state.build_window_options)(None, cx);
5765                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
5766                cx.open_window(options, |cx| {
5767                    cx.new_view(|cx| {
5768                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5769                    })
5770                })
5771            })??
5772        };
5773
5774        workspace.update(&mut cx, |workspace, cx| {
5775            cx.activate(true);
5776            cx.activate_window();
5777
5778            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5779                let follow_peer_id = room
5780                    .read(cx)
5781                    .remote_participants()
5782                    .iter()
5783                    .find(|(_, participant)| participant.user.id == follow_user_id)
5784                    .map(|(_, p)| p.peer_id)
5785                    .or_else(|| {
5786                        // If we couldn't follow the given user, follow the host instead.
5787                        let collaborator = workspace
5788                            .project()
5789                            .read(cx)
5790                            .collaborators()
5791                            .values()
5792                            .find(|collaborator| collaborator.replica_id == 0)?;
5793                        Some(collaborator.peer_id)
5794                    });
5795
5796                if let Some(follow_peer_id) = follow_peer_id {
5797                    workspace.follow(follow_peer_id, cx);
5798                }
5799            }
5800        })?;
5801
5802        anyhow::Ok(())
5803    })
5804}
5805
5806pub fn reload(reload: &Reload, cx: &mut AppContext) {
5807    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5808    let mut workspace_windows = cx
5809        .windows()
5810        .into_iter()
5811        .filter_map(|window| window.downcast::<Workspace>())
5812        .collect::<Vec<_>>();
5813
5814    // If multiple windows have unsaved changes, and need a save prompt,
5815    // prompt in the active window before switching to a different window.
5816    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5817
5818    let mut prompt = None;
5819    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5820        prompt = window
5821            .update(cx, |_, cx| {
5822                cx.prompt(
5823                    PromptLevel::Info,
5824                    "Are you sure you want to restart?",
5825                    None,
5826                    &["Restart", "Cancel"],
5827                )
5828            })
5829            .ok();
5830    }
5831
5832    let binary_path = reload.binary_path.clone();
5833    cx.spawn(|mut cx| async move {
5834        if let Some(prompt) = prompt {
5835            let answer = prompt.await?;
5836            if answer != 0 {
5837                return Ok(());
5838            }
5839        }
5840
5841        // If the user cancels any save prompt, then keep the app open.
5842        for window in workspace_windows {
5843            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5844                workspace.prepare_to_close(CloseIntent::Quit, cx)
5845            }) {
5846                if !should_close.await? {
5847                    return Ok(());
5848                }
5849            }
5850        }
5851
5852        cx.update(|cx| cx.restart(binary_path))
5853    })
5854    .detach_and_log_err(cx);
5855}
5856
5857fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
5858    let mut parts = value.split(',');
5859    let x: usize = parts.next()?.parse().ok()?;
5860    let y: usize = parts.next()?.parse().ok()?;
5861    Some(point(px(x as f32), px(y as f32)))
5862}
5863
5864fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
5865    let mut parts = value.split(',');
5866    let width: usize = parts.next()?.parse().ok()?;
5867    let height: usize = parts.next()?.parse().ok()?;
5868    Some(size(px(width as f32), px(height as f32)))
5869}
5870
5871pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext) -> Stateful<Div> {
5872    const BORDER_SIZE: Pixels = px(1.0);
5873    let decorations = cx.window_decorations();
5874
5875    if matches!(decorations, Decorations::Client { .. }) {
5876        cx.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
5877    }
5878
5879    struct GlobalResizeEdge(ResizeEdge);
5880    impl Global for GlobalResizeEdge {}
5881
5882    div()
5883        .id("window-backdrop")
5884        .bg(transparent_black())
5885        .map(|div| match decorations {
5886            Decorations::Server => div,
5887            Decorations::Client { tiling, .. } => div
5888                .when(!(tiling.top || tiling.right), |div| {
5889                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5890                })
5891                .when(!(tiling.top || tiling.left), |div| {
5892                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5893                })
5894                .when(!(tiling.bottom || tiling.right), |div| {
5895                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5896                })
5897                .when(!(tiling.bottom || tiling.left), |div| {
5898                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5899                })
5900                .when(!tiling.top, |div| {
5901                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
5902                })
5903                .when(!tiling.bottom, |div| {
5904                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
5905                })
5906                .when(!tiling.left, |div| {
5907                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
5908                })
5909                .when(!tiling.right, |div| {
5910                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
5911                })
5912                .on_mouse_move(move |e, cx| {
5913                    let size = cx.window_bounds().get_bounds().size;
5914                    let pos = e.position;
5915
5916                    let new_edge =
5917                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
5918
5919                    let edge = cx.try_global::<GlobalResizeEdge>();
5920                    if new_edge != edge.map(|edge| edge.0) {
5921                        cx.window_handle()
5922                            .update(cx, |workspace, cx| cx.notify(workspace.entity_id()))
5923                            .ok();
5924                    }
5925                })
5926                .on_mouse_down(MouseButton::Left, move |e, cx| {
5927                    let size = cx.window_bounds().get_bounds().size;
5928                    let pos = e.position;
5929
5930                    let edge = match resize_edge(
5931                        pos,
5932                        theme::CLIENT_SIDE_DECORATION_SHADOW,
5933                        size,
5934                        tiling,
5935                    ) {
5936                        Some(value) => value,
5937                        None => return,
5938                    };
5939
5940                    cx.start_window_resize(edge);
5941                }),
5942        })
5943        .size_full()
5944        .child(
5945            div()
5946                .cursor(CursorStyle::Arrow)
5947                .map(|div| match decorations {
5948                    Decorations::Server => div,
5949                    Decorations::Client { tiling } => div
5950                        .border_color(cx.theme().colors().border)
5951                        .when(!(tiling.top || tiling.right), |div| {
5952                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5953                        })
5954                        .when(!(tiling.top || tiling.left), |div| {
5955                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5956                        })
5957                        .when(!(tiling.bottom || tiling.right), |div| {
5958                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5959                        })
5960                        .when(!(tiling.bottom || tiling.left), |div| {
5961                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5962                        })
5963                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
5964                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
5965                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
5966                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
5967                        .when(!tiling.is_tiled(), |div| {
5968                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
5969                                color: Hsla {
5970                                    h: 0.,
5971                                    s: 0.,
5972                                    l: 0.,
5973                                    a: 0.4,
5974                                },
5975                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
5976                                spread_radius: px(0.),
5977                                offset: point(px(0.0), px(0.0)),
5978                            }])
5979                        }),
5980                })
5981                .on_mouse_move(|_e, cx| {
5982                    cx.stop_propagation();
5983                })
5984                .size_full()
5985                .child(element),
5986        )
5987        .map(|div| match decorations {
5988            Decorations::Server => div,
5989            Decorations::Client { tiling, .. } => div.child(
5990                canvas(
5991                    |_bounds, cx| {
5992                        cx.insert_hitbox(
5993                            Bounds::new(
5994                                point(px(0.0), px(0.0)),
5995                                cx.window_bounds().get_bounds().size,
5996                            ),
5997                            false,
5998                        )
5999                    },
6000                    move |_bounds, hitbox, cx| {
6001                        let mouse = cx.mouse_position();
6002                        let size = cx.window_bounds().get_bounds().size;
6003                        let Some(edge) =
6004                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
6005                        else {
6006                            return;
6007                        };
6008                        cx.set_global(GlobalResizeEdge(edge));
6009                        cx.set_cursor_style(
6010                            match edge {
6011                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
6012                                ResizeEdge::Left | ResizeEdge::Right => {
6013                                    CursorStyle::ResizeLeftRight
6014                                }
6015                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
6016                                    CursorStyle::ResizeUpLeftDownRight
6017                                }
6018                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
6019                                    CursorStyle::ResizeUpRightDownLeft
6020                                }
6021                            },
6022                            &hitbox,
6023                        );
6024                    },
6025                )
6026                .size_full()
6027                .absolute(),
6028            ),
6029        })
6030}
6031
6032fn resize_edge(
6033    pos: Point<Pixels>,
6034    shadow_size: Pixels,
6035    window_size: Size<Pixels>,
6036    tiling: Tiling,
6037) -> Option<ResizeEdge> {
6038    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
6039    if bounds.contains(&pos) {
6040        return None;
6041    }
6042
6043    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
6044    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
6045    if !tiling.top && top_left_bounds.contains(&pos) {
6046        return Some(ResizeEdge::TopLeft);
6047    }
6048
6049    let top_right_bounds = Bounds::new(
6050        Point::new(window_size.width - corner_size.width, px(0.)),
6051        corner_size,
6052    );
6053    if !tiling.top && top_right_bounds.contains(&pos) {
6054        return Some(ResizeEdge::TopRight);
6055    }
6056
6057    let bottom_left_bounds = Bounds::new(
6058        Point::new(px(0.), window_size.height - corner_size.height),
6059        corner_size,
6060    );
6061    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
6062        return Some(ResizeEdge::BottomLeft);
6063    }
6064
6065    let bottom_right_bounds = Bounds::new(
6066        Point::new(
6067            window_size.width - corner_size.width,
6068            window_size.height - corner_size.height,
6069        ),
6070        corner_size,
6071    );
6072    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
6073        return Some(ResizeEdge::BottomRight);
6074    }
6075
6076    if !tiling.top && pos.y < shadow_size {
6077        Some(ResizeEdge::Top)
6078    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
6079        Some(ResizeEdge::Bottom)
6080    } else if !tiling.left && pos.x < shadow_size {
6081        Some(ResizeEdge::Left)
6082    } else if !tiling.right && pos.x > window_size.width - shadow_size {
6083        Some(ResizeEdge::Right)
6084    } else {
6085        None
6086    }
6087}
6088
6089fn join_pane_into_active(active_pane: &View<Pane>, pane: &View<Pane>, cx: &mut WindowContext<'_>) {
6090    if pane == active_pane {
6091        return;
6092    } else if pane.read(cx).items_len() == 0 {
6093        pane.update(cx, |_, cx| {
6094            cx.emit(pane::Event::Remove {
6095                focus_on_pane: None,
6096            });
6097        })
6098    } else {
6099        move_all_items(pane, active_pane, cx);
6100    }
6101}
6102
6103fn move_all_items(from_pane: &View<Pane>, to_pane: &View<Pane>, cx: &mut WindowContext<'_>) {
6104    let destination_is_different = from_pane != to_pane;
6105    let mut moved_items = 0;
6106    for (item_ix, item_handle) in from_pane
6107        .read(cx)
6108        .items()
6109        .enumerate()
6110        .map(|(ix, item)| (ix, item.clone()))
6111        .collect::<Vec<_>>()
6112    {
6113        let ix = item_ix - moved_items;
6114        if destination_is_different {
6115            // Close item from previous pane
6116            from_pane.update(cx, |source, cx| {
6117                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), cx);
6118            });
6119            moved_items += 1;
6120        }
6121
6122        // This automatically removes duplicate items in the pane
6123        to_pane.update(cx, |destination, cx| {
6124            destination.add_item(item_handle, true, true, None, cx);
6125            destination.focus(cx)
6126        });
6127    }
6128}
6129
6130pub fn move_item(
6131    source: &View<Pane>,
6132    destination: &View<Pane>,
6133    item_id_to_move: EntityId,
6134    destination_index: usize,
6135    cx: &mut WindowContext<'_>,
6136) {
6137    let Some((item_ix, item_handle)) = source
6138        .read(cx)
6139        .items()
6140        .enumerate()
6141        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
6142        .map(|(ix, item)| (ix, item.clone()))
6143    else {
6144        // Tab was closed during drag
6145        return;
6146    };
6147
6148    if source != destination {
6149        // Close item from previous pane
6150        source.update(cx, |source, cx| {
6151            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), cx);
6152        });
6153    }
6154
6155    // This automatically removes duplicate items in the pane
6156    destination.update(cx, |destination, cx| {
6157        destination.add_item(item_handle, true, true, Some(destination_index), cx);
6158        destination.focus(cx)
6159    });
6160}
6161
6162#[cfg(test)]
6163mod tests {
6164    use std::{cell::RefCell, rc::Rc};
6165
6166    use super::*;
6167    use crate::{
6168        dock::{test::TestPanel, PanelEvent},
6169        item::{
6170            test::{TestItem, TestProjectItem},
6171            ItemEvent,
6172        },
6173    };
6174    use fs::FakeFs;
6175    use gpui::{
6176        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
6177        UpdateGlobal, VisualTestContext,
6178    };
6179    use project::{Project, ProjectEntryId};
6180    use serde_json::json;
6181    use settings::SettingsStore;
6182
6183    #[gpui::test]
6184    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
6185        init_test(cx);
6186
6187        let fs = FakeFs::new(cx.executor());
6188        let project = Project::test(fs, [], cx).await;
6189        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6190
6191        // Adding an item with no ambiguity renders the tab without detail.
6192        let item1 = cx.new_view(|cx| {
6193            let mut item = TestItem::new(cx);
6194            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
6195            item
6196        });
6197        workspace.update(cx, |workspace, cx| {
6198            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6199        });
6200        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
6201
6202        // Adding an item that creates ambiguity increases the level of detail on
6203        // both tabs.
6204        let item2 = cx.new_view(|cx| {
6205            let mut item = TestItem::new(cx);
6206            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
6207            item
6208        });
6209        workspace.update(cx, |workspace, cx| {
6210            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6211        });
6212        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6213        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6214
6215        // Adding an item that creates ambiguity increases the level of detail only
6216        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
6217        // we stop at the highest detail available.
6218        let item3 = cx.new_view(|cx| {
6219            let mut item = TestItem::new(cx);
6220            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
6221            item
6222        });
6223        workspace.update(cx, |workspace, cx| {
6224            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6225        });
6226        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6227        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6228        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6229    }
6230
6231    #[gpui::test]
6232    async fn test_tracking_active_path(cx: &mut TestAppContext) {
6233        init_test(cx);
6234
6235        let fs = FakeFs::new(cx.executor());
6236        fs.insert_tree(
6237            "/root1",
6238            json!({
6239                "one.txt": "",
6240                "two.txt": "",
6241            }),
6242        )
6243        .await;
6244        fs.insert_tree(
6245            "/root2",
6246            json!({
6247                "three.txt": "",
6248            }),
6249        )
6250        .await;
6251
6252        let project = Project::test(fs, ["root1".as_ref()], cx).await;
6253        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6254        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6255        let worktree_id = project.update(cx, |project, cx| {
6256            project.worktrees(cx).next().unwrap().read(cx).id()
6257        });
6258
6259        let item1 = cx.new_view(|cx| {
6260            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
6261        });
6262        let item2 = cx.new_view(|cx| {
6263            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
6264        });
6265
6266        // Add an item to an empty pane
6267        workspace.update(cx, |workspace, cx| {
6268            workspace.add_item_to_active_pane(Box::new(item1), None, true, cx)
6269        });
6270        project.update(cx, |project, cx| {
6271            assert_eq!(
6272                project.active_entry(),
6273                project
6274                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6275                    .map(|e| e.id)
6276            );
6277        });
6278        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
6279
6280        // Add a second item to a non-empty pane
6281        workspace.update(cx, |workspace, cx| {
6282            workspace.add_item_to_active_pane(Box::new(item2), None, true, cx)
6283        });
6284        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
6285        project.update(cx, |project, cx| {
6286            assert_eq!(
6287                project.active_entry(),
6288                project
6289                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
6290                    .map(|e| e.id)
6291            );
6292        });
6293
6294        // Close the active item
6295        pane.update(cx, |pane, cx| {
6296            pane.close_active_item(&Default::default(), cx).unwrap()
6297        })
6298        .await
6299        .unwrap();
6300        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
6301        project.update(cx, |project, cx| {
6302            assert_eq!(
6303                project.active_entry(),
6304                project
6305                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6306                    .map(|e| e.id)
6307            );
6308        });
6309
6310        // Add a project folder
6311        project
6312            .update(cx, |project, cx| {
6313                project.find_or_create_worktree("root2", true, cx)
6314            })
6315            .await
6316            .unwrap();
6317        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
6318
6319        // Remove a project folder
6320        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
6321        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
6322    }
6323
6324    #[gpui::test]
6325    async fn test_close_window(cx: &mut TestAppContext) {
6326        init_test(cx);
6327
6328        let fs = FakeFs::new(cx.executor());
6329        fs.insert_tree("/root", json!({ "one": "" })).await;
6330
6331        let project = Project::test(fs, ["root".as_ref()], cx).await;
6332        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6333
6334        // When there are no dirty items, there's nothing to do.
6335        let item1 = cx.new_view(TestItem::new);
6336        workspace.update(cx, |w, cx| {
6337            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx)
6338        });
6339        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6340        assert!(task.await.unwrap());
6341
6342        // When there are dirty untitled items, prompt to save each one. If the user
6343        // cancels any prompt, then abort.
6344        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
6345        let item3 = cx.new_view(|cx| {
6346            TestItem::new(cx)
6347                .with_dirty(true)
6348                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6349        });
6350        workspace.update(cx, |w, cx| {
6351            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6352            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6353        });
6354        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6355        cx.executor().run_until_parked();
6356        cx.simulate_prompt_answer(2); // cancel save all
6357        cx.executor().run_until_parked();
6358        cx.simulate_prompt_answer(2); // cancel save all
6359        cx.executor().run_until_parked();
6360        assert!(!cx.has_pending_prompt());
6361        assert!(!task.await.unwrap());
6362    }
6363
6364    #[gpui::test]
6365    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
6366        init_test(cx);
6367
6368        // Register TestItem as a serializable item
6369        cx.update(|cx| {
6370            register_serializable_item::<TestItem>(cx);
6371        });
6372
6373        let fs = FakeFs::new(cx.executor());
6374        fs.insert_tree("/root", json!({ "one": "" })).await;
6375
6376        let project = Project::test(fs, ["root".as_ref()], cx).await;
6377        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6378
6379        // When there are dirty untitled items, but they can serialize, then there is no prompt.
6380        let item1 = cx.new_view(|cx| {
6381            TestItem::new(cx)
6382                .with_dirty(true)
6383                .with_serialize(|| Some(Task::ready(Ok(()))))
6384        });
6385        let item2 = cx.new_view(|cx| {
6386            TestItem::new(cx)
6387                .with_dirty(true)
6388                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6389                .with_serialize(|| Some(Task::ready(Ok(()))))
6390        });
6391        workspace.update(cx, |w, cx| {
6392            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6393            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6394        });
6395        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6396        assert!(task.await.unwrap());
6397    }
6398
6399    #[gpui::test]
6400    async fn test_close_pane_items(cx: &mut TestAppContext) {
6401        init_test(cx);
6402
6403        let fs = FakeFs::new(cx.executor());
6404
6405        let project = Project::test(fs, None, cx).await;
6406        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6407
6408        let item1 = cx.new_view(|cx| {
6409            TestItem::new(cx)
6410                .with_dirty(true)
6411                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6412        });
6413        let item2 = cx.new_view(|cx| {
6414            TestItem::new(cx)
6415                .with_dirty(true)
6416                .with_conflict(true)
6417                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
6418        });
6419        let item3 = cx.new_view(|cx| {
6420            TestItem::new(cx)
6421                .with_dirty(true)
6422                .with_conflict(true)
6423                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
6424        });
6425        let item4 = cx.new_view(|cx| {
6426            TestItem::new(cx)
6427                .with_dirty(true)
6428                .with_project_items(&[TestProjectItem::new_untitled(cx)])
6429        });
6430        let pane = workspace.update(cx, |workspace, cx| {
6431            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6432            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6433            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6434            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, cx);
6435            workspace.active_pane().clone()
6436        });
6437
6438        let close_items = pane.update(cx, |pane, cx| {
6439            pane.activate_item(1, true, true, cx);
6440            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6441            let item1_id = item1.item_id();
6442            let item3_id = item3.item_id();
6443            let item4_id = item4.item_id();
6444            pane.close_items(cx, SaveIntent::Close, move |id| {
6445                [item1_id, item3_id, item4_id].contains(&id)
6446            })
6447        });
6448        cx.executor().run_until_parked();
6449
6450        assert!(cx.has_pending_prompt());
6451        // Ignore "Save all" prompt
6452        cx.simulate_prompt_answer(2);
6453        cx.executor().run_until_parked();
6454        // There's a prompt to save item 1.
6455        pane.update(cx, |pane, _| {
6456            assert_eq!(pane.items_len(), 4);
6457            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
6458        });
6459        // Confirm saving item 1.
6460        cx.simulate_prompt_answer(0);
6461        cx.executor().run_until_parked();
6462
6463        // Item 1 is saved. There's a prompt to save item 3.
6464        pane.update(cx, |pane, cx| {
6465            assert_eq!(item1.read(cx).save_count, 1);
6466            assert_eq!(item1.read(cx).save_as_count, 0);
6467            assert_eq!(item1.read(cx).reload_count, 0);
6468            assert_eq!(pane.items_len(), 3);
6469            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
6470        });
6471        assert!(cx.has_pending_prompt());
6472
6473        // Cancel saving item 3.
6474        cx.simulate_prompt_answer(1);
6475        cx.executor().run_until_parked();
6476
6477        // Item 3 is reloaded. There's a prompt to save item 4.
6478        pane.update(cx, |pane, cx| {
6479            assert_eq!(item3.read(cx).save_count, 0);
6480            assert_eq!(item3.read(cx).save_as_count, 0);
6481            assert_eq!(item3.read(cx).reload_count, 1);
6482            assert_eq!(pane.items_len(), 2);
6483            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
6484        });
6485        assert!(cx.has_pending_prompt());
6486
6487        // Confirm saving item 4.
6488        cx.simulate_prompt_answer(0);
6489        cx.executor().run_until_parked();
6490
6491        // There's a prompt for a path for item 4.
6492        cx.simulate_new_path_selection(|_| Some(Default::default()));
6493        close_items.await.unwrap();
6494
6495        // The requested items are closed.
6496        pane.update(cx, |pane, cx| {
6497            assert_eq!(item4.read(cx).save_count, 0);
6498            assert_eq!(item4.read(cx).save_as_count, 1);
6499            assert_eq!(item4.read(cx).reload_count, 0);
6500            assert_eq!(pane.items_len(), 1);
6501            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6502        });
6503    }
6504
6505    #[gpui::test]
6506    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
6507        init_test(cx);
6508
6509        let fs = FakeFs::new(cx.executor());
6510        let project = Project::test(fs, [], cx).await;
6511        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6512
6513        // Create several workspace items with single project entries, and two
6514        // workspace items with multiple project entries.
6515        let single_entry_items = (0..=4)
6516            .map(|project_entry_id| {
6517                cx.new_view(|cx| {
6518                    TestItem::new(cx)
6519                        .with_dirty(true)
6520                        .with_project_items(&[TestProjectItem::new(
6521                            project_entry_id,
6522                            &format!("{project_entry_id}.txt"),
6523                            cx,
6524                        )])
6525                })
6526            })
6527            .collect::<Vec<_>>();
6528        let item_2_3 = cx.new_view(|cx| {
6529            TestItem::new(cx)
6530                .with_dirty(true)
6531                .with_singleton(false)
6532                .with_project_items(&[
6533                    single_entry_items[2].read(cx).project_items[0].clone(),
6534                    single_entry_items[3].read(cx).project_items[0].clone(),
6535                ])
6536        });
6537        let item_3_4 = cx.new_view(|cx| {
6538            TestItem::new(cx)
6539                .with_dirty(true)
6540                .with_singleton(false)
6541                .with_project_items(&[
6542                    single_entry_items[3].read(cx).project_items[0].clone(),
6543                    single_entry_items[4].read(cx).project_items[0].clone(),
6544                ])
6545        });
6546
6547        // Create two panes that contain the following project entries:
6548        //   left pane:
6549        //     multi-entry items:   (2, 3)
6550        //     single-entry items:  0, 1, 2, 3, 4
6551        //   right pane:
6552        //     single-entry items:  1
6553        //     multi-entry items:   (3, 4)
6554        let left_pane = workspace.update(cx, |workspace, cx| {
6555            let left_pane = workspace.active_pane().clone();
6556            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, cx);
6557            for item in single_entry_items {
6558                workspace.add_item_to_active_pane(Box::new(item), None, true, cx);
6559            }
6560            left_pane.update(cx, |pane, cx| {
6561                pane.activate_item(2, true, true, cx);
6562            });
6563
6564            let right_pane = workspace
6565                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
6566                .unwrap();
6567
6568            right_pane.update(cx, |pane, cx| {
6569                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
6570            });
6571
6572            left_pane
6573        });
6574
6575        cx.focus_view(&left_pane);
6576
6577        // When closing all of the items in the left pane, we should be prompted twice:
6578        // once for project entry 0, and once for project entry 2. Project entries 1,
6579        // 3, and 4 are all still open in the other paten. After those two
6580        // prompts, the task should complete.
6581
6582        let close = left_pane.update(cx, |pane, cx| {
6583            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
6584        });
6585        cx.executor().run_until_parked();
6586
6587        // Discard "Save all" prompt
6588        cx.simulate_prompt_answer(2);
6589
6590        cx.executor().run_until_parked();
6591        left_pane.update(cx, |pane, cx| {
6592            assert_eq!(
6593                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6594                &[ProjectEntryId::from_proto(0)]
6595            );
6596        });
6597        cx.simulate_prompt_answer(0);
6598
6599        cx.executor().run_until_parked();
6600        left_pane.update(cx, |pane, cx| {
6601            assert_eq!(
6602                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6603                &[ProjectEntryId::from_proto(2)]
6604            );
6605        });
6606        cx.simulate_prompt_answer(0);
6607
6608        cx.executor().run_until_parked();
6609        close.await.unwrap();
6610        left_pane.update(cx, |pane, _| {
6611            assert_eq!(pane.items_len(), 0);
6612        });
6613    }
6614
6615    #[gpui::test]
6616    async fn test_autosave(cx: &mut gpui::TestAppContext) {
6617        init_test(cx);
6618
6619        let fs = FakeFs::new(cx.executor());
6620        let project = Project::test(fs, [], cx).await;
6621        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6622        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6623
6624        let item = cx.new_view(|cx| {
6625            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6626        });
6627        let item_id = item.entity_id();
6628        workspace.update(cx, |workspace, cx| {
6629            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6630        });
6631
6632        // Autosave on window change.
6633        item.update(cx, |item, cx| {
6634            SettingsStore::update_global(cx, |settings, cx| {
6635                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6636                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
6637                })
6638            });
6639            item.is_dirty = true;
6640        });
6641
6642        // Deactivating the window saves the file.
6643        cx.deactivate_window();
6644        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6645
6646        // Re-activating the window doesn't save the file.
6647        cx.update(|cx| cx.activate_window());
6648        cx.executor().run_until_parked();
6649        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6650
6651        // Autosave on focus change.
6652        item.update(cx, |item, cx| {
6653            cx.focus_self();
6654            SettingsStore::update_global(cx, |settings, cx| {
6655                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6656                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6657                })
6658            });
6659            item.is_dirty = true;
6660        });
6661
6662        // Blurring the item saves the file.
6663        item.update(cx, |_, cx| cx.blur());
6664        cx.executor().run_until_parked();
6665        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
6666
6667        // Deactivating the window still saves the file.
6668        item.update(cx, |item, cx| {
6669            cx.focus_self();
6670            item.is_dirty = true;
6671        });
6672        cx.deactivate_window();
6673        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6674
6675        // Autosave after delay.
6676        item.update(cx, |item, cx| {
6677            SettingsStore::update_global(cx, |settings, cx| {
6678                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6679                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
6680                })
6681            });
6682            item.is_dirty = true;
6683            cx.emit(ItemEvent::Edit);
6684        });
6685
6686        // Delay hasn't fully expired, so the file is still dirty and unsaved.
6687        cx.executor().advance_clock(Duration::from_millis(250));
6688        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6689
6690        // After delay expires, the file is saved.
6691        cx.executor().advance_clock(Duration::from_millis(250));
6692        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
6693
6694        // Autosave on focus change, ensuring closing the tab counts as such.
6695        item.update(cx, |item, cx| {
6696            SettingsStore::update_global(cx, |settings, cx| {
6697                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6698                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6699                })
6700            });
6701            item.is_dirty = true;
6702        });
6703
6704        pane.update(cx, |pane, cx| {
6705            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6706        })
6707        .await
6708        .unwrap();
6709        assert!(!cx.has_pending_prompt());
6710        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6711
6712        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
6713        workspace.update(cx, |workspace, cx| {
6714            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6715        });
6716        item.update(cx, |item, cx| {
6717            item.project_items[0].update(cx, |item, _| {
6718                item.entry_id = None;
6719            });
6720            item.is_dirty = true;
6721            cx.blur();
6722        });
6723        cx.run_until_parked();
6724        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6725
6726        // Ensure autosave is prevented for deleted files also when closing the buffer.
6727        let _close_items = pane.update(cx, |pane, cx| {
6728            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6729        });
6730        cx.run_until_parked();
6731        assert!(cx.has_pending_prompt());
6732        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6733    }
6734
6735    #[gpui::test]
6736    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
6737        init_test(cx);
6738
6739        let fs = FakeFs::new(cx.executor());
6740
6741        let project = Project::test(fs, [], cx).await;
6742        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6743
6744        let item = cx.new_view(|cx| {
6745            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6746        });
6747        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6748        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
6749        let toolbar_notify_count = Rc::new(RefCell::new(0));
6750
6751        workspace.update(cx, |workspace, cx| {
6752            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6753            let toolbar_notification_count = toolbar_notify_count.clone();
6754            cx.observe(&toolbar, move |_, _, _| {
6755                *toolbar_notification_count.borrow_mut() += 1
6756            })
6757            .detach();
6758        });
6759
6760        pane.update(cx, |pane, _| {
6761            assert!(!pane.can_navigate_backward());
6762            assert!(!pane.can_navigate_forward());
6763        });
6764
6765        item.update(cx, |item, cx| {
6766            item.set_state("one".to_string(), cx);
6767        });
6768
6769        // Toolbar must be notified to re-render the navigation buttons
6770        assert_eq!(*toolbar_notify_count.borrow(), 1);
6771
6772        pane.update(cx, |pane, _| {
6773            assert!(pane.can_navigate_backward());
6774            assert!(!pane.can_navigate_forward());
6775        });
6776
6777        workspace
6778            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
6779            .await
6780            .unwrap();
6781
6782        assert_eq!(*toolbar_notify_count.borrow(), 2);
6783        pane.update(cx, |pane, _| {
6784            assert!(!pane.can_navigate_backward());
6785            assert!(pane.can_navigate_forward());
6786        });
6787    }
6788
6789    #[gpui::test]
6790    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
6791        init_test(cx);
6792        let fs = FakeFs::new(cx.executor());
6793
6794        let project = Project::test(fs, [], cx).await;
6795        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6796
6797        let panel = workspace.update(cx, |workspace, cx| {
6798            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6799            workspace.add_panel(panel.clone(), cx);
6800
6801            workspace
6802                .right_dock()
6803                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6804
6805            panel
6806        });
6807
6808        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6809        pane.update(cx, |pane, cx| {
6810            let item = cx.new_view(TestItem::new);
6811            pane.add_item(Box::new(item), true, true, None, cx);
6812        });
6813
6814        // Transfer focus from center to panel
6815        workspace.update(cx, |workspace, cx| {
6816            workspace.toggle_panel_focus::<TestPanel>(cx);
6817        });
6818
6819        workspace.update(cx, |workspace, cx| {
6820            assert!(workspace.right_dock().read(cx).is_open());
6821            assert!(!panel.is_zoomed(cx));
6822            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6823        });
6824
6825        // Transfer focus from panel to center
6826        workspace.update(cx, |workspace, cx| {
6827            workspace.toggle_panel_focus::<TestPanel>(cx);
6828        });
6829
6830        workspace.update(cx, |workspace, cx| {
6831            assert!(workspace.right_dock().read(cx).is_open());
6832            assert!(!panel.is_zoomed(cx));
6833            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6834        });
6835
6836        // Close the dock
6837        workspace.update(cx, |workspace, cx| {
6838            workspace.toggle_dock(DockPosition::Right, cx);
6839        });
6840
6841        workspace.update(cx, |workspace, cx| {
6842            assert!(!workspace.right_dock().read(cx).is_open());
6843            assert!(!panel.is_zoomed(cx));
6844            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6845        });
6846
6847        // Open the dock
6848        workspace.update(cx, |workspace, cx| {
6849            workspace.toggle_dock(DockPosition::Right, cx);
6850        });
6851
6852        workspace.update(cx, |workspace, cx| {
6853            assert!(workspace.right_dock().read(cx).is_open());
6854            assert!(!panel.is_zoomed(cx));
6855            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6856        });
6857
6858        // Focus and zoom panel
6859        panel.update(cx, |panel, cx| {
6860            cx.focus_self();
6861            panel.set_zoomed(true, cx)
6862        });
6863
6864        workspace.update(cx, |workspace, cx| {
6865            assert!(workspace.right_dock().read(cx).is_open());
6866            assert!(panel.is_zoomed(cx));
6867            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6868        });
6869
6870        // Transfer focus to the center closes the dock
6871        workspace.update(cx, |workspace, cx| {
6872            workspace.toggle_panel_focus::<TestPanel>(cx);
6873        });
6874
6875        workspace.update(cx, |workspace, cx| {
6876            assert!(!workspace.right_dock().read(cx).is_open());
6877            assert!(panel.is_zoomed(cx));
6878            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6879        });
6880
6881        // Transferring focus back to the panel keeps it zoomed
6882        workspace.update(cx, |workspace, cx| {
6883            workspace.toggle_panel_focus::<TestPanel>(cx);
6884        });
6885
6886        workspace.update(cx, |workspace, cx| {
6887            assert!(workspace.right_dock().read(cx).is_open());
6888            assert!(panel.is_zoomed(cx));
6889            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6890        });
6891
6892        // Close the dock while it is zoomed
6893        workspace.update(cx, |workspace, cx| {
6894            workspace.toggle_dock(DockPosition::Right, cx)
6895        });
6896
6897        workspace.update(cx, |workspace, cx| {
6898            assert!(!workspace.right_dock().read(cx).is_open());
6899            assert!(panel.is_zoomed(cx));
6900            assert!(workspace.zoomed.is_none());
6901            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6902        });
6903
6904        // Opening the dock, when it's zoomed, retains focus
6905        workspace.update(cx, |workspace, cx| {
6906            workspace.toggle_dock(DockPosition::Right, cx)
6907        });
6908
6909        workspace.update(cx, |workspace, cx| {
6910            assert!(workspace.right_dock().read(cx).is_open());
6911            assert!(panel.is_zoomed(cx));
6912            assert!(workspace.zoomed.is_some());
6913            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6914        });
6915
6916        // Unzoom and close the panel, zoom the active pane.
6917        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
6918        workspace.update(cx, |workspace, cx| {
6919            workspace.toggle_dock(DockPosition::Right, cx)
6920        });
6921        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
6922
6923        // Opening a dock unzooms the pane.
6924        workspace.update(cx, |workspace, cx| {
6925            workspace.toggle_dock(DockPosition::Right, cx)
6926        });
6927        workspace.update(cx, |workspace, cx| {
6928            let pane = pane.read(cx);
6929            assert!(!pane.is_zoomed());
6930            assert!(!pane.focus_handle(cx).is_focused(cx));
6931            assert!(workspace.right_dock().read(cx).is_open());
6932            assert!(workspace.zoomed.is_none());
6933        });
6934    }
6935
6936    #[gpui::test]
6937    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
6938        init_test(cx);
6939
6940        let fs = FakeFs::new(cx.executor());
6941
6942        let project = Project::test(fs, None, cx).await;
6943        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6944
6945        // Let's arrange the panes like this:
6946        //
6947        // +-----------------------+
6948        // |         top           |
6949        // +------+--------+-------+
6950        // | left | center | right |
6951        // +------+--------+-------+
6952        // |        bottom         |
6953        // +-----------------------+
6954
6955        let top_item = cx.new_view(|cx| {
6956            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
6957        });
6958        let bottom_item = cx.new_view(|cx| {
6959            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
6960        });
6961        let left_item = cx.new_view(|cx| {
6962            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
6963        });
6964        let right_item = cx.new_view(|cx| {
6965            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
6966        });
6967        let center_item = cx.new_view(|cx| {
6968            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
6969        });
6970
6971        let top_pane_id = workspace.update(cx, |workspace, cx| {
6972            let top_pane_id = workspace.active_pane().entity_id();
6973            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, cx);
6974            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Down, cx);
6975            top_pane_id
6976        });
6977        let bottom_pane_id = workspace.update(cx, |workspace, cx| {
6978            let bottom_pane_id = workspace.active_pane().entity_id();
6979            workspace.add_item_to_active_pane(Box::new(bottom_item.clone()), None, false, cx);
6980            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Up, cx);
6981            bottom_pane_id
6982        });
6983        let left_pane_id = workspace.update(cx, |workspace, cx| {
6984            let left_pane_id = workspace.active_pane().entity_id();
6985            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, cx);
6986            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
6987            left_pane_id
6988        });
6989        let right_pane_id = workspace.update(cx, |workspace, cx| {
6990            let right_pane_id = workspace.active_pane().entity_id();
6991            workspace.add_item_to_active_pane(Box::new(right_item.clone()), None, false, cx);
6992            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Left, cx);
6993            right_pane_id
6994        });
6995        let center_pane_id = workspace.update(cx, |workspace, cx| {
6996            let center_pane_id = workspace.active_pane().entity_id();
6997            workspace.add_item_to_active_pane(Box::new(center_item.clone()), None, false, cx);
6998            center_pane_id
6999        });
7000        cx.executor().run_until_parked();
7001
7002        workspace.update(cx, |workspace, cx| {
7003            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
7004
7005            // Join into next from center pane into right
7006            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
7007        });
7008
7009        workspace.update(cx, |workspace, cx| {
7010            let active_pane = workspace.active_pane();
7011            assert_eq!(right_pane_id, active_pane.entity_id());
7012            assert_eq!(2, active_pane.read(cx).items_len());
7013            let item_ids_in_pane =
7014                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
7015            assert!(item_ids_in_pane.contains(&center_item.item_id()));
7016            assert!(item_ids_in_pane.contains(&right_item.item_id()));
7017
7018            // Join into next from right pane into bottom
7019            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
7020        });
7021
7022        workspace.update(cx, |workspace, cx| {
7023            let active_pane = workspace.active_pane();
7024            assert_eq!(bottom_pane_id, active_pane.entity_id());
7025            assert_eq!(3, active_pane.read(cx).items_len());
7026            let item_ids_in_pane =
7027                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
7028            assert!(item_ids_in_pane.contains(&center_item.item_id()));
7029            assert!(item_ids_in_pane.contains(&right_item.item_id()));
7030            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
7031
7032            // Join into next from bottom pane into left
7033            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
7034        });
7035
7036        workspace.update(cx, |workspace, cx| {
7037            let active_pane = workspace.active_pane();
7038            assert_eq!(left_pane_id, active_pane.entity_id());
7039            assert_eq!(4, active_pane.read(cx).items_len());
7040            let item_ids_in_pane =
7041                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
7042            assert!(item_ids_in_pane.contains(&center_item.item_id()));
7043            assert!(item_ids_in_pane.contains(&right_item.item_id()));
7044            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
7045            assert!(item_ids_in_pane.contains(&left_item.item_id()));
7046
7047            // Join into next from left pane into top
7048            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
7049        });
7050
7051        workspace.update(cx, |workspace, cx| {
7052            let active_pane = workspace.active_pane();
7053            assert_eq!(top_pane_id, active_pane.entity_id());
7054            assert_eq!(5, active_pane.read(cx).items_len());
7055            let item_ids_in_pane =
7056                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
7057            assert!(item_ids_in_pane.contains(&center_item.item_id()));
7058            assert!(item_ids_in_pane.contains(&right_item.item_id()));
7059            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
7060            assert!(item_ids_in_pane.contains(&left_item.item_id()));
7061            assert!(item_ids_in_pane.contains(&top_item.item_id()));
7062
7063            // Single pane left: no-op
7064            workspace.join_pane_into_next(workspace.active_pane().clone(), cx)
7065        });
7066
7067        workspace.update(cx, |workspace, _cx| {
7068            let active_pane = workspace.active_pane();
7069            assert_eq!(top_pane_id, active_pane.entity_id());
7070        });
7071    }
7072
7073    fn add_an_item_to_active_pane(
7074        cx: &mut VisualTestContext,
7075        workspace: &View<Workspace>,
7076        item_id: u64,
7077    ) -> View<TestItem> {
7078        let item = cx.new_view(|cx| {
7079            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
7080                item_id,
7081                "item{item_id}.txt",
7082                cx,
7083            )])
7084        });
7085        workspace.update(cx, |workspace, cx| {
7086            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, cx);
7087        });
7088        return item;
7089    }
7090
7091    fn split_pane(cx: &mut VisualTestContext, workspace: &View<Workspace>) -> View<Pane> {
7092        return workspace.update(cx, |workspace, cx| {
7093            let new_pane =
7094                workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
7095            new_pane
7096        });
7097    }
7098
7099    #[gpui::test]
7100    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
7101        init_test(cx);
7102        let fs = FakeFs::new(cx.executor());
7103        let project = Project::test(fs, None, cx).await;
7104        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
7105
7106        add_an_item_to_active_pane(cx, &workspace, 1);
7107        split_pane(cx, &workspace);
7108        add_an_item_to_active_pane(cx, &workspace, 2);
7109        split_pane(cx, &workspace); // empty pane
7110        split_pane(cx, &workspace);
7111        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
7112
7113        cx.executor().run_until_parked();
7114
7115        workspace.update(cx, |workspace, cx| {
7116            let num_panes = workspace.panes().len();
7117            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
7118            let active_item = workspace
7119                .active_pane()
7120                .read(cx)
7121                .active_item()
7122                .expect("item is in focus");
7123
7124            assert_eq!(num_panes, 4);
7125            assert_eq!(num_items_in_current_pane, 1);
7126            assert_eq!(active_item.item_id(), last_item.item_id());
7127        });
7128
7129        workspace.update(cx, |workspace, cx| {
7130            workspace.join_all_panes(cx);
7131        });
7132
7133        workspace.update(cx, |workspace, cx| {
7134            let num_panes = workspace.panes().len();
7135            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
7136            let active_item = workspace
7137                .active_pane()
7138                .read(cx)
7139                .active_item()
7140                .expect("item is in focus");
7141
7142            assert_eq!(num_panes, 1);
7143            assert_eq!(num_items_in_current_pane, 3);
7144            assert_eq!(active_item.item_id(), last_item.item_id());
7145        });
7146    }
7147    struct TestModal(FocusHandle);
7148
7149    impl TestModal {
7150        fn new(cx: &mut ViewContext<Self>) -> Self {
7151            Self(cx.focus_handle())
7152        }
7153    }
7154
7155    impl EventEmitter<DismissEvent> for TestModal {}
7156
7157    impl FocusableView for TestModal {
7158        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7159            self.0.clone()
7160        }
7161    }
7162
7163    impl ModalView for TestModal {}
7164
7165    impl Render for TestModal {
7166        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
7167            div().track_focus(&self.0)
7168        }
7169    }
7170
7171    #[gpui::test]
7172    async fn test_panels(cx: &mut gpui::TestAppContext) {
7173        init_test(cx);
7174        let fs = FakeFs::new(cx.executor());
7175
7176        let project = Project::test(fs, [], cx).await;
7177        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
7178
7179        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
7180            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
7181            workspace.add_panel(panel_1.clone(), cx);
7182            workspace
7183                .left_dock()
7184                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
7185            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
7186            workspace.add_panel(panel_2.clone(), cx);
7187            workspace
7188                .right_dock()
7189                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
7190
7191            let left_dock = workspace.left_dock();
7192            assert_eq!(
7193                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7194                panel_1.panel_id()
7195            );
7196            assert_eq!(
7197                left_dock.read(cx).active_panel_size(cx).unwrap(),
7198                panel_1.size(cx)
7199            );
7200
7201            left_dock.update(cx, |left_dock, cx| {
7202                left_dock.resize_active_panel(Some(px(1337.)), cx)
7203            });
7204            assert_eq!(
7205                workspace
7206                    .right_dock()
7207                    .read(cx)
7208                    .visible_panel()
7209                    .unwrap()
7210                    .panel_id(),
7211                panel_2.panel_id(),
7212            );
7213
7214            (panel_1, panel_2)
7215        });
7216
7217        // Move panel_1 to the right
7218        panel_1.update(cx, |panel_1, cx| {
7219            panel_1.set_position(DockPosition::Right, cx)
7220        });
7221
7222        workspace.update(cx, |workspace, cx| {
7223            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
7224            // Since it was the only panel on the left, the left dock should now be closed.
7225            assert!(!workspace.left_dock().read(cx).is_open());
7226            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
7227            let right_dock = workspace.right_dock();
7228            assert_eq!(
7229                right_dock.read(cx).visible_panel().unwrap().panel_id(),
7230                panel_1.panel_id()
7231            );
7232            assert_eq!(
7233                right_dock.read(cx).active_panel_size(cx).unwrap(),
7234                px(1337.)
7235            );
7236
7237            // Now we move panel_2 to the left
7238            panel_2.set_position(DockPosition::Left, cx);
7239        });
7240
7241        workspace.update(cx, |workspace, cx| {
7242            // Since panel_2 was not visible on the right, we don't open the left dock.
7243            assert!(!workspace.left_dock().read(cx).is_open());
7244            // And the right dock is unaffected in its displaying of panel_1
7245            assert!(workspace.right_dock().read(cx).is_open());
7246            assert_eq!(
7247                workspace
7248                    .right_dock()
7249                    .read(cx)
7250                    .visible_panel()
7251                    .unwrap()
7252                    .panel_id(),
7253                panel_1.panel_id(),
7254            );
7255        });
7256
7257        // Move panel_1 back to the left
7258        panel_1.update(cx, |panel_1, cx| {
7259            panel_1.set_position(DockPosition::Left, cx)
7260        });
7261
7262        workspace.update(cx, |workspace, cx| {
7263            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
7264            let left_dock = workspace.left_dock();
7265            assert!(left_dock.read(cx).is_open());
7266            assert_eq!(
7267                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7268                panel_1.panel_id()
7269            );
7270            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
7271            // And the right dock should be closed as it no longer has any panels.
7272            assert!(!workspace.right_dock().read(cx).is_open());
7273
7274            // Now we move panel_1 to the bottom
7275            panel_1.set_position(DockPosition::Bottom, cx);
7276        });
7277
7278        workspace.update(cx, |workspace, cx| {
7279            // Since panel_1 was visible on the left, we close the left dock.
7280            assert!(!workspace.left_dock().read(cx).is_open());
7281            // The bottom dock is sized based on the panel's default size,
7282            // since the panel orientation changed from vertical to horizontal.
7283            let bottom_dock = workspace.bottom_dock();
7284            assert_eq!(
7285                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
7286                panel_1.size(cx),
7287            );
7288            // Close bottom dock and move panel_1 back to the left.
7289            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
7290            panel_1.set_position(DockPosition::Left, cx);
7291        });
7292
7293        // Emit activated event on panel 1
7294        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
7295
7296        // Now the left dock is open and panel_1 is active and focused.
7297        workspace.update(cx, |workspace, cx| {
7298            let left_dock = workspace.left_dock();
7299            assert!(left_dock.read(cx).is_open());
7300            assert_eq!(
7301                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7302                panel_1.panel_id(),
7303            );
7304            assert!(panel_1.focus_handle(cx).is_focused(cx));
7305        });
7306
7307        // Emit closed event on panel 2, which is not active
7308        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7309
7310        // Wo don't close the left dock, because panel_2 wasn't the active panel
7311        workspace.update(cx, |workspace, cx| {
7312            let left_dock = workspace.left_dock();
7313            assert!(left_dock.read(cx).is_open());
7314            assert_eq!(
7315                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7316                panel_1.panel_id(),
7317            );
7318        });
7319
7320        // Emitting a ZoomIn event shows the panel as zoomed.
7321        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
7322        workspace.update(cx, |workspace, _| {
7323            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7324            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
7325        });
7326
7327        // Move panel to another dock while it is zoomed
7328        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
7329        workspace.update(cx, |workspace, _| {
7330            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7331
7332            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7333        });
7334
7335        // This is a helper for getting a:
7336        // - valid focus on an element,
7337        // - that isn't a part of the panes and panels system of the Workspace,
7338        // - and doesn't trigger the 'on_focus_lost' API.
7339        let focus_other_view = {
7340            let workspace = workspace.clone();
7341            move |cx: &mut VisualTestContext| {
7342                workspace.update(cx, |workspace, cx| {
7343                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
7344                        workspace.toggle_modal(cx, TestModal::new);
7345                        workspace.toggle_modal(cx, TestModal::new);
7346                    } else {
7347                        workspace.toggle_modal(cx, TestModal::new);
7348                    }
7349                })
7350            }
7351        };
7352
7353        // If focus is transferred to another view that's not a panel or another pane, we still show
7354        // the panel as zoomed.
7355        focus_other_view(cx);
7356        workspace.update(cx, |workspace, _| {
7357            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7358            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7359        });
7360
7361        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
7362        workspace.update(cx, |_, cx| cx.focus_self());
7363        workspace.update(cx, |workspace, _| {
7364            assert_eq!(workspace.zoomed, None);
7365            assert_eq!(workspace.zoomed_position, None);
7366        });
7367
7368        // If focus is transferred again to another view that's not a panel or a pane, we won't
7369        // show the panel as zoomed because it wasn't zoomed before.
7370        focus_other_view(cx);
7371        workspace.update(cx, |workspace, _| {
7372            assert_eq!(workspace.zoomed, None);
7373            assert_eq!(workspace.zoomed_position, None);
7374        });
7375
7376        // When the panel is activated, it is zoomed again.
7377        cx.dispatch_action(ToggleRightDock);
7378        workspace.update(cx, |workspace, _| {
7379            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7380            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7381        });
7382
7383        // Emitting a ZoomOut event unzooms the panel.
7384        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
7385        workspace.update(cx, |workspace, _| {
7386            assert_eq!(workspace.zoomed, None);
7387            assert_eq!(workspace.zoomed_position, None);
7388        });
7389
7390        // Emit closed event on panel 1, which is active
7391        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7392
7393        // Now the left dock is closed, because panel_1 was the active panel
7394        workspace.update(cx, |workspace, cx| {
7395            let right_dock = workspace.right_dock();
7396            assert!(!right_dock.read(cx).is_open());
7397        });
7398    }
7399
7400    mod register_project_item_tests {
7401        use ui::Context as _;
7402
7403        use super::*;
7404
7405        // View
7406        struct TestPngItemView {
7407            focus_handle: FocusHandle,
7408        }
7409        // Model
7410        struct TestPngItem {}
7411
7412        impl project::Item for TestPngItem {
7413            fn try_open(
7414                _project: &Model<Project>,
7415                path: &ProjectPath,
7416                cx: &mut AppContext,
7417            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7418                if path.path.extension().unwrap() == "png" {
7419                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
7420                } else {
7421                    None
7422                }
7423            }
7424
7425            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7426                None
7427            }
7428
7429            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7430                None
7431            }
7432        }
7433
7434        impl Item for TestPngItemView {
7435            type Event = ();
7436        }
7437        impl EventEmitter<()> for TestPngItemView {}
7438        impl FocusableView for TestPngItemView {
7439            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7440                self.focus_handle.clone()
7441            }
7442        }
7443
7444        impl Render for TestPngItemView {
7445            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7446                Empty
7447            }
7448        }
7449
7450        impl ProjectItem for TestPngItemView {
7451            type Item = TestPngItem;
7452
7453            fn for_project_item(
7454                _project: Model<Project>,
7455                _item: Model<Self::Item>,
7456                cx: &mut ViewContext<Self>,
7457            ) -> Self
7458            where
7459                Self: Sized,
7460            {
7461                Self {
7462                    focus_handle: cx.focus_handle(),
7463                }
7464            }
7465        }
7466
7467        // View
7468        struct TestIpynbItemView {
7469            focus_handle: FocusHandle,
7470        }
7471        // Model
7472        struct TestIpynbItem {}
7473
7474        impl project::Item for TestIpynbItem {
7475            fn try_open(
7476                _project: &Model<Project>,
7477                path: &ProjectPath,
7478                cx: &mut AppContext,
7479            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7480                if path.path.extension().unwrap() == "ipynb" {
7481                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
7482                } else {
7483                    None
7484                }
7485            }
7486
7487            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7488                None
7489            }
7490
7491            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7492                None
7493            }
7494        }
7495
7496        impl Item for TestIpynbItemView {
7497            type Event = ();
7498        }
7499        impl EventEmitter<()> for TestIpynbItemView {}
7500        impl FocusableView for TestIpynbItemView {
7501            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7502                self.focus_handle.clone()
7503            }
7504        }
7505
7506        impl Render for TestIpynbItemView {
7507            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7508                Empty
7509            }
7510        }
7511
7512        impl ProjectItem for TestIpynbItemView {
7513            type Item = TestIpynbItem;
7514
7515            fn for_project_item(
7516                _project: Model<Project>,
7517                _item: Model<Self::Item>,
7518                cx: &mut ViewContext<Self>,
7519            ) -> Self
7520            where
7521                Self: Sized,
7522            {
7523                Self {
7524                    focus_handle: cx.focus_handle(),
7525                }
7526            }
7527        }
7528
7529        struct TestAlternatePngItemView {
7530            focus_handle: FocusHandle,
7531        }
7532
7533        impl Item for TestAlternatePngItemView {
7534            type Event = ();
7535        }
7536
7537        impl EventEmitter<()> for TestAlternatePngItemView {}
7538        impl FocusableView for TestAlternatePngItemView {
7539            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7540                self.focus_handle.clone()
7541            }
7542        }
7543
7544        impl Render for TestAlternatePngItemView {
7545            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7546                Empty
7547            }
7548        }
7549
7550        impl ProjectItem for TestAlternatePngItemView {
7551            type Item = TestPngItem;
7552
7553            fn for_project_item(
7554                _project: Model<Project>,
7555                _item: Model<Self::Item>,
7556                cx: &mut ViewContext<Self>,
7557            ) -> Self
7558            where
7559                Self: Sized,
7560            {
7561                Self {
7562                    focus_handle: cx.focus_handle(),
7563                }
7564            }
7565        }
7566
7567        #[gpui::test]
7568        async fn test_register_project_item(cx: &mut TestAppContext) {
7569            init_test(cx);
7570
7571            cx.update(|cx| {
7572                register_project_item::<TestPngItemView>(cx);
7573                register_project_item::<TestIpynbItemView>(cx);
7574            });
7575
7576            let fs = FakeFs::new(cx.executor());
7577            fs.insert_tree(
7578                "/root1",
7579                json!({
7580                    "one.png": "BINARYDATAHERE",
7581                    "two.ipynb": "{ totally a notebook }",
7582                    "three.txt": "editing text, sure why not?"
7583                }),
7584            )
7585            .await;
7586
7587            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7588            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7589
7590            let worktree_id = project.update(cx, |project, cx| {
7591                project.worktrees(cx).next().unwrap().read(cx).id()
7592            });
7593
7594            let handle = workspace
7595                .update(cx, |workspace, cx| {
7596                    let project_path = (worktree_id, "one.png");
7597                    workspace.open_path(project_path, None, true, cx)
7598                })
7599                .await
7600                .unwrap();
7601
7602            // Now we can check if the handle we got back errored or not
7603            assert_eq!(
7604                handle.to_any().entity_type(),
7605                TypeId::of::<TestPngItemView>()
7606            );
7607
7608            let handle = workspace
7609                .update(cx, |workspace, cx| {
7610                    let project_path = (worktree_id, "two.ipynb");
7611                    workspace.open_path(project_path, None, true, cx)
7612                })
7613                .await
7614                .unwrap();
7615
7616            assert_eq!(
7617                handle.to_any().entity_type(),
7618                TypeId::of::<TestIpynbItemView>()
7619            );
7620
7621            let handle = workspace
7622                .update(cx, |workspace, cx| {
7623                    let project_path = (worktree_id, "three.txt");
7624                    workspace.open_path(project_path, None, true, cx)
7625                })
7626                .await;
7627            assert!(handle.is_err());
7628        }
7629
7630        #[gpui::test]
7631        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
7632            init_test(cx);
7633
7634            cx.update(|cx| {
7635                register_project_item::<TestPngItemView>(cx);
7636                register_project_item::<TestAlternatePngItemView>(cx);
7637            });
7638
7639            let fs = FakeFs::new(cx.executor());
7640            fs.insert_tree(
7641                "/root1",
7642                json!({
7643                    "one.png": "BINARYDATAHERE",
7644                    "two.ipynb": "{ totally a notebook }",
7645                    "three.txt": "editing text, sure why not?"
7646                }),
7647            )
7648            .await;
7649
7650            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7651            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7652
7653            let worktree_id = project.update(cx, |project, cx| {
7654                project.worktrees(cx).next().unwrap().read(cx).id()
7655            });
7656
7657            let handle = workspace
7658                .update(cx, |workspace, cx| {
7659                    let project_path = (worktree_id, "one.png");
7660                    workspace.open_path(project_path, None, true, cx)
7661                })
7662                .await
7663                .unwrap();
7664
7665            // This _must_ be the second item registered
7666            assert_eq!(
7667                handle.to_any().entity_type(),
7668                TypeId::of::<TestAlternatePngItemView>()
7669            );
7670
7671            let handle = workspace
7672                .update(cx, |workspace, cx| {
7673                    let project_path = (worktree_id, "three.txt");
7674                    workspace.open_path(project_path, None, true, cx)
7675                })
7676                .await;
7677            assert!(handle.is_err());
7678        }
7679    }
7680
7681    pub fn init_test(cx: &mut TestAppContext) {
7682        cx.update(|cx| {
7683            let settings_store = SettingsStore::test(cx);
7684            cx.set_global(settings_store);
7685            theme::init(theme::LoadThemes::JustBase, cx);
7686            language::init(cx);
7687            crate::init_settings(cx);
7688            Project::init_settings(cx);
7689        });
7690    }
7691}