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