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