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