workspace.rs

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