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