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