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