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