workspace.rs

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