workspace.rs

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