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