workspace.rs

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