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