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