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