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::persistence::{
  98    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  99    SerializedAxis,
 100};
 101use crate::{notifications::NotificationId, persistence::model::LocalPathsOrder};
 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                let (order, paths): (Vec<_>, Vec<_>) = local_paths
3947                    .iter()
3948                    .enumerate()
3949                    .sorted_by(|a, b| a.1.cmp(b.1))
3950                    .unzip();
3951
3952                Some(SerializedWorkspaceLocation::Local(
3953                    LocalPaths::new(paths),
3954                    LocalPathsOrder::new(order),
3955                ))
3956            } else {
3957                None
3958            }
3959        } else if let Some(dev_server_project_id) = self.project().read(cx).dev_server_project_id()
3960        {
3961            let store = dev_server_projects::Store::global(cx).read(cx);
3962            maybe!({
3963                let project = store.dev_server_project(dev_server_project_id)?;
3964                let dev_server = store.dev_server(project.dev_server_id)?;
3965
3966                let dev_server_project = SerializedDevServerProject {
3967                    id: dev_server_project_id,
3968                    dev_server_name: dev_server.name.to_string(),
3969                    paths: project.paths.iter().map(|path| path.clone()).collect(),
3970                };
3971                Some(SerializedWorkspaceLocation::DevServer(dev_server_project))
3972            })
3973        } else {
3974            None
3975        };
3976
3977        // don't save workspace state for the empty workspace.
3978        if let Some(location) = location {
3979            let center_group = build_serialized_pane_group(&self.center.root, cx);
3980            let docks = build_serialized_docks(self, cx);
3981            let window_bounds = Some(SerializedWindowBounds(cx.window_bounds()));
3982            let serialized_workspace = SerializedWorkspace {
3983                id: database_id,
3984                location,
3985                center_group,
3986                window_bounds,
3987                display: Default::default(),
3988                docks,
3989                centered_layout: self.centered_layout,
3990            };
3991            return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
3992        }
3993        Task::ready(())
3994    }
3995
3996    async fn serialize_items(
3997        this: &WeakView<Self>,
3998        items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
3999        cx: &mut AsyncWindowContext,
4000    ) -> Result<()> {
4001        const CHUNK_SIZE: usize = 200;
4002        const THROTTLE_TIME: Duration = Duration::from_millis(200);
4003
4004        let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
4005
4006        while let Some(items_received) = serializable_items.next().await {
4007            let unique_items =
4008                items_received
4009                    .into_iter()
4010                    .fold(HashMap::default(), |mut acc, item| {
4011                        acc.entry(item.item_id()).or_insert(item);
4012                        acc
4013                    });
4014
4015            // We use into_iter() here so that the references to the items are moved into
4016            // the tasks and not kept alive while we're sleeping.
4017            for (_, item) in unique_items.into_iter() {
4018                if let Ok(Some(task)) =
4019                    this.update(cx, |workspace, cx| item.serialize(workspace, false, cx))
4020                {
4021                    cx.background_executor()
4022                        .spawn(async move { task.await.log_err() })
4023                        .detach();
4024                }
4025            }
4026
4027            cx.background_executor().timer(THROTTLE_TIME).await;
4028        }
4029
4030        Ok(())
4031    }
4032
4033    pub(crate) fn enqueue_item_serialization(
4034        &mut self,
4035        item: Box<dyn SerializableItemHandle>,
4036    ) -> Result<()> {
4037        self.serializable_items_tx
4038            .unbounded_send(item)
4039            .map_err(|err| anyhow!("failed to send serializable item over channel: {}", err))
4040    }
4041
4042    pub(crate) fn load_workspace(
4043        serialized_workspace: SerializedWorkspace,
4044        paths_to_open: Vec<Option<ProjectPath>>,
4045        cx: &mut ViewContext<Workspace>,
4046    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
4047        cx.spawn(|workspace, mut cx| async move {
4048            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
4049
4050            let mut center_group = None;
4051            let mut center_items = None;
4052
4053            // Traverse the splits tree and add to things
4054            if let Some((group, active_pane, items)) = serialized_workspace
4055                .center_group
4056                .deserialize(
4057                    &project,
4058                    serialized_workspace.id,
4059                    workspace.clone(),
4060                    &mut cx,
4061                )
4062                .await
4063            {
4064                center_items = Some(items);
4065                center_group = Some((group, active_pane))
4066            }
4067
4068            let mut items_by_project_path = HashMap::default();
4069            let mut item_ids_by_kind = HashMap::default();
4070            let mut all_deserialized_items = Vec::default();
4071            cx.update(|cx| {
4072                for item in center_items.unwrap_or_default().into_iter().flatten() {
4073                    if let Some(serializable_item_handle) = item.to_serializable_item_handle(cx) {
4074                        item_ids_by_kind
4075                            .entry(serializable_item_handle.serialized_item_kind())
4076                            .or_insert(Vec::new())
4077                            .push(item.item_id().as_u64() as ItemId);
4078                    }
4079
4080                    if let Some(project_path) = item.project_path(cx) {
4081                        items_by_project_path.insert(project_path, item.clone());
4082                    }
4083                    all_deserialized_items.push(item);
4084                }
4085            })?;
4086
4087            let opened_items = paths_to_open
4088                .into_iter()
4089                .map(|path_to_open| {
4090                    path_to_open
4091                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
4092                })
4093                .collect::<Vec<_>>();
4094
4095            // Remove old panes from workspace panes list
4096            workspace.update(&mut cx, |workspace, cx| {
4097                if let Some((center_group, active_pane)) = center_group {
4098                    workspace.remove_panes(workspace.center.root.clone(), cx);
4099
4100                    // Swap workspace center group
4101                    workspace.center = PaneGroup::with_root(center_group);
4102                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
4103                    if let Some(active_pane) = active_pane {
4104                        workspace.active_pane = active_pane;
4105                        cx.focus_self();
4106                    } else {
4107                        workspace.active_pane = workspace.center.first_pane().clone();
4108                    }
4109                }
4110
4111                let docks = serialized_workspace.docks;
4112
4113                for (dock, serialized_dock) in [
4114                    (&mut workspace.right_dock, docks.right),
4115                    (&mut workspace.left_dock, docks.left),
4116                    (&mut workspace.bottom_dock, docks.bottom),
4117                ]
4118                .iter_mut()
4119                {
4120                    dock.update(cx, |dock, cx| {
4121                        dock.serialized_dock = Some(serialized_dock.clone());
4122                        dock.restore_state(cx);
4123                    });
4124                }
4125
4126                cx.notify();
4127            })?;
4128
4129            // Clean up all the items that have _not_ been loaded. Our ItemIds aren't stable. That means
4130            // after loading the items, we might have different items and in order to avoid
4131            // the database filling up, we delete items that haven't been loaded now.
4132            //
4133            // The items that have been loaded, have been saved after they've been added to the workspace.
4134            let clean_up_tasks = workspace.update(&mut cx, |_, cx| {
4135                item_ids_by_kind
4136                    .into_iter()
4137                    .map(|(item_kind, loaded_items)| {
4138                        SerializableItemRegistry::cleanup(
4139                            item_kind,
4140                            serialized_workspace.id,
4141                            loaded_items,
4142                            cx,
4143                        )
4144                        .log_err()
4145                    })
4146                    .collect::<Vec<_>>()
4147            })?;
4148
4149            futures::future::join_all(clean_up_tasks).await;
4150
4151            workspace
4152                .update(&mut cx, |workspace, cx| {
4153                    // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
4154                    workspace.serialize_workspace_internal(cx).detach();
4155
4156                    // Ensure that we mark the window as edited if we did load dirty items
4157                    workspace.update_window_edited(cx);
4158                })
4159                .ok();
4160
4161            Ok(opened_items)
4162        })
4163    }
4164
4165    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
4166        self.add_workspace_actions_listeners(div, cx)
4167            .on_action(cx.listener(Self::close_inactive_items_and_panes))
4168            .on_action(cx.listener(Self::close_all_items_and_panes))
4169            .on_action(cx.listener(Self::save_all))
4170            .on_action(cx.listener(Self::send_keystrokes))
4171            .on_action(cx.listener(Self::add_folder_to_project))
4172            .on_action(cx.listener(Self::follow_next_collaborator))
4173            .on_action(cx.listener(Self::open))
4174            .on_action(cx.listener(Self::close_window))
4175            .on_action(cx.listener(Self::activate_pane_at_index))
4176            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
4177                let pane = workspace.active_pane().clone();
4178                workspace.unfollow_in_pane(&pane, cx);
4179            }))
4180            .on_action(cx.listener(|workspace, action: &Save, cx| {
4181                workspace
4182                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
4183                    .detach_and_log_err(cx);
4184            }))
4185            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
4186                workspace
4187                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
4188                    .detach_and_log_err(cx);
4189            }))
4190            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
4191                workspace
4192                    .save_active_item(SaveIntent::SaveAs, cx)
4193                    .detach_and_log_err(cx);
4194            }))
4195            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
4196                workspace.activate_previous_pane(cx)
4197            }))
4198            .on_action(
4199                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
4200            )
4201            .on_action(
4202                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
4203                    workspace.activate_pane_in_direction(action.0, cx)
4204                }),
4205            )
4206            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
4207                workspace.swap_pane_in_direction(action.0, cx)
4208            }))
4209            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
4210                this.toggle_dock(DockPosition::Left, cx);
4211            }))
4212            .on_action(
4213                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
4214                    workspace.toggle_dock(DockPosition::Right, cx);
4215                }),
4216            )
4217            .on_action(
4218                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
4219                    workspace.toggle_dock(DockPosition::Bottom, cx);
4220                }),
4221            )
4222            .on_action(
4223                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
4224                    workspace.close_all_docks(cx);
4225                }),
4226            )
4227            .on_action(
4228                cx.listener(|workspace: &mut Workspace, _: &ClearAllNotifications, cx| {
4229                    workspace.clear_all_notifications(cx);
4230                }),
4231            )
4232            .on_action(
4233                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
4234                    workspace.reopen_closed_item(cx).detach();
4235                }),
4236            )
4237            .on_action(cx.listener(Workspace::toggle_centered_layout))
4238    }
4239
4240    #[cfg(any(test, feature = "test-support"))]
4241    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
4242        use node_runtime::FakeNodeRuntime;
4243
4244        let client = project.read(cx).client();
4245        let user_store = project.read(cx).user_store();
4246
4247        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
4248        cx.activate_window();
4249        let app_state = Arc::new(AppState {
4250            languages: project.read(cx).languages().clone(),
4251            workspace_store,
4252            client,
4253            user_store,
4254            fs: project.read(cx).fs().clone(),
4255            build_window_options: |_, _| Default::default(),
4256            node_runtime: FakeNodeRuntime::new(),
4257        });
4258        let workspace = Self::new(Default::default(), project, app_state, cx);
4259        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
4260        workspace
4261    }
4262
4263    pub fn register_action<A: Action>(
4264        &mut self,
4265        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
4266    ) -> &mut Self {
4267        let callback = Arc::new(callback);
4268
4269        self.workspace_actions.push(Box::new(move |div, cx| {
4270            let callback = callback.clone();
4271            div.on_action(
4272                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
4273            )
4274        }));
4275        self
4276    }
4277
4278    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
4279        for action in self.workspace_actions.iter() {
4280            div = (action)(div, cx)
4281        }
4282        div
4283    }
4284
4285    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
4286        self.modal_layer.read(cx).has_active_modal()
4287    }
4288
4289    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
4290        self.modal_layer.read(cx).active_modal()
4291    }
4292
4293    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
4294    where
4295        B: FnOnce(&mut ViewContext<V>) -> V,
4296    {
4297        self.modal_layer
4298            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
4299    }
4300
4301    pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
4302        self.centered_layout = !self.centered_layout;
4303        if let Some(database_id) = self.database_id() {
4304            cx.background_executor()
4305                .spawn(DB.set_centered_layout(database_id, self.centered_layout))
4306                .detach_and_log_err(cx);
4307        }
4308        cx.notify();
4309    }
4310
4311    fn adjust_padding(padding: Option<f32>) -> f32 {
4312        padding
4313            .unwrap_or(Self::DEFAULT_PADDING)
4314            .clamp(0.0, Self::MAX_PADDING)
4315    }
4316
4317    fn render_dock(
4318        &self,
4319        position: DockPosition,
4320        dock: &View<Dock>,
4321        cx: &WindowContext,
4322    ) -> Option<Div> {
4323        if self.zoomed_position == Some(position) {
4324            return None;
4325        }
4326
4327        let leader_border = dock.read(cx).active_panel().and_then(|panel| {
4328            let pane = panel.pane(cx)?;
4329            let follower_states = &self.follower_states;
4330            leader_border_for_pane(follower_states, &pane, cx)
4331        });
4332
4333        Some(
4334            div()
4335                .flex()
4336                .flex_none()
4337                .overflow_hidden()
4338                .child(dock.clone())
4339                .children(leader_border),
4340        )
4341    }
4342}
4343
4344fn leader_border_for_pane(
4345    follower_states: &HashMap<PeerId, FollowerState>,
4346    pane: &View<Pane>,
4347    cx: &WindowContext,
4348) -> Option<Div> {
4349    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
4350        if state.pane() == pane {
4351            Some((*leader_id, state))
4352        } else {
4353            None
4354        }
4355    })?;
4356
4357    let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
4358    let leader = room.remote_participant_for_peer_id(leader_id)?;
4359
4360    let mut leader_color = cx
4361        .theme()
4362        .players()
4363        .color_for_participant(leader.participant_index.0)
4364        .cursor;
4365    leader_color.fade_out(0.3);
4366    Some(
4367        div()
4368            .absolute()
4369            .size_full()
4370            .left_0()
4371            .top_0()
4372            .border_2()
4373            .border_color(leader_color),
4374    )
4375}
4376
4377fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
4378    ZED_WINDOW_POSITION
4379        .zip(*ZED_WINDOW_SIZE)
4380        .map(|(position, size)| Bounds {
4381            origin: position,
4382            size,
4383        })
4384}
4385
4386fn open_items(
4387    serialized_workspace: Option<SerializedWorkspace>,
4388    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
4389    app_state: Arc<AppState>,
4390    cx: &mut ViewContext<Workspace>,
4391) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
4392    let restored_items = serialized_workspace.map(|serialized_workspace| {
4393        Workspace::load_workspace(
4394            serialized_workspace,
4395            project_paths_to_open
4396                .iter()
4397                .map(|(_, project_path)| project_path)
4398                .cloned()
4399                .collect(),
4400            cx,
4401        )
4402    });
4403
4404    cx.spawn(|workspace, mut cx| async move {
4405        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
4406
4407        if let Some(restored_items) = restored_items {
4408            let restored_items = restored_items.await?;
4409
4410            let restored_project_paths = restored_items
4411                .iter()
4412                .filter_map(|item| {
4413                    cx.update(|cx| item.as_ref()?.project_path(cx))
4414                        .ok()
4415                        .flatten()
4416                })
4417                .collect::<HashSet<_>>();
4418
4419            for restored_item in restored_items {
4420                opened_items.push(restored_item.map(Ok));
4421            }
4422
4423            project_paths_to_open
4424                .iter_mut()
4425                .for_each(|(_, project_path)| {
4426                    if let Some(project_path_to_open) = project_path {
4427                        if restored_project_paths.contains(project_path_to_open) {
4428                            *project_path = None;
4429                        }
4430                    }
4431                });
4432        } else {
4433            for _ in 0..project_paths_to_open.len() {
4434                opened_items.push(None);
4435            }
4436        }
4437        assert!(opened_items.len() == project_paths_to_open.len());
4438
4439        let tasks =
4440            project_paths_to_open
4441                .into_iter()
4442                .enumerate()
4443                .map(|(ix, (abs_path, project_path))| {
4444                    let workspace = workspace.clone();
4445                    cx.spawn(|mut cx| {
4446                        let fs = app_state.fs.clone();
4447                        async move {
4448                            let file_project_path = project_path?;
4449                            if fs.is_dir(&abs_path).await {
4450                                None
4451                            } else {
4452                                Some((
4453                                    ix,
4454                                    workspace
4455                                        .update(&mut cx, |workspace, cx| {
4456                                            workspace.open_path(file_project_path, None, true, cx)
4457                                        })
4458                                        .log_err()?
4459                                        .await,
4460                                ))
4461                            }
4462                        }
4463                    })
4464                });
4465
4466        let tasks = tasks.collect::<Vec<_>>();
4467
4468        let tasks = futures::future::join_all(tasks);
4469        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
4470            opened_items[ix] = Some(path_open_result);
4471        }
4472
4473        Ok(opened_items)
4474    })
4475}
4476
4477enum ActivateInDirectionTarget {
4478    Pane(View<Pane>),
4479    Dock(View<Dock>),
4480}
4481
4482fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
4483    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
4484
4485    workspace
4486        .update(cx, |workspace, cx| {
4487            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
4488                struct DatabaseFailedNotification;
4489
4490                workspace.show_notification_once(
4491                    NotificationId::unique::<DatabaseFailedNotification>(),
4492                    cx,
4493                    |cx| {
4494                        cx.new_view(|_| {
4495                            MessageNotification::new("Failed to load the database file.")
4496                                .with_click_message("Click to let us know about this error")
4497                                .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
4498                        })
4499                    },
4500                );
4501            }
4502        })
4503        .log_err();
4504}
4505
4506impl FocusableView for Workspace {
4507    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4508        self.active_pane.focus_handle(cx)
4509    }
4510}
4511
4512#[derive(Clone, Render)]
4513struct DraggedDock(DockPosition);
4514
4515impl Render for Workspace {
4516    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4517        let mut context = KeyContext::new_with_defaults();
4518        context.add("Workspace");
4519        let centered_layout = self.centered_layout
4520            && self.center.panes().len() == 1
4521            && self.active_item(cx).is_some();
4522        let render_padding = |size| {
4523            (size > 0.0).then(|| {
4524                div()
4525                    .h_full()
4526                    .w(relative(size))
4527                    .bg(cx.theme().colors().editor_background)
4528                    .border_color(cx.theme().colors().pane_group_border)
4529            })
4530        };
4531        let paddings = if centered_layout {
4532            let settings = WorkspaceSettings::get_global(cx).centered_layout;
4533            (
4534                render_padding(Self::adjust_padding(settings.left_padding)),
4535                render_padding(Self::adjust_padding(settings.right_padding)),
4536            )
4537        } else {
4538            (None, None)
4539        };
4540        let ui_font = theme::setup_ui_font(cx);
4541
4542        let theme = cx.theme().clone();
4543        let colors = theme.colors();
4544
4545        client_side_decorations(
4546            self.actions(div(), cx)
4547                .key_context(context)
4548                .relative()
4549                .size_full()
4550                .flex()
4551                .flex_col()
4552                .font(ui_font)
4553                .gap_0()
4554                .justify_start()
4555                .items_start()
4556                .text_color(colors.text)
4557                .overflow_hidden()
4558                .children(self.titlebar_item.clone())
4559                .child(
4560                    div()
4561                        .id("workspace")
4562                        .bg(colors.background)
4563                        .relative()
4564                        .flex_1()
4565                        .w_full()
4566                        .flex()
4567                        .flex_col()
4568                        .overflow_hidden()
4569                        .border_t_1()
4570                        .border_b_1()
4571                        .border_color(colors.border)
4572                        .child({
4573                            let this = cx.view().clone();
4574                            canvas(
4575                                move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
4576                                |_, _, _| {},
4577                            )
4578                            .absolute()
4579                            .size_full()
4580                        })
4581                        .when(self.zoomed.is_none(), |this| {
4582                            this.on_drag_move(cx.listener(
4583                                |workspace, e: &DragMoveEvent<DraggedDock>, cx| match e.drag(cx).0 {
4584                                    DockPosition::Left => {
4585                                        let size = e.event.position.x - workspace.bounds.left();
4586                                        workspace.left_dock.update(cx, |left_dock, cx| {
4587                                            left_dock.resize_active_panel(Some(size), cx);
4588                                        });
4589                                    }
4590                                    DockPosition::Right => {
4591                                        let size = workspace.bounds.right() - e.event.position.x;
4592                                        workspace.right_dock.update(cx, |right_dock, cx| {
4593                                            right_dock.resize_active_panel(Some(size), cx);
4594                                        });
4595                                    }
4596                                    DockPosition::Bottom => {
4597                                        let size = workspace.bounds.bottom() - e.event.position.y;
4598                                        workspace.bottom_dock.update(cx, |bottom_dock, cx| {
4599                                            bottom_dock.resize_active_panel(Some(size), cx);
4600                                        });
4601                                    }
4602                                },
4603                            ))
4604                        })
4605                        .child(
4606                            div()
4607                                .flex()
4608                                .flex_row()
4609                                .h_full()
4610                                // Left Dock
4611                                .children(self.render_dock(DockPosition::Left, &self.left_dock, cx))
4612                                // Panes
4613                                .child(
4614                                    div()
4615                                        .flex()
4616                                        .flex_col()
4617                                        .flex_1()
4618                                        .overflow_hidden()
4619                                        .child(
4620                                            h_flex()
4621                                                .flex_1()
4622                                                .when_some(paddings.0, |this, p| {
4623                                                    this.child(p.border_r_1())
4624                                                })
4625                                                .child(self.center.render(
4626                                                    &self.project,
4627                                                    &self.follower_states,
4628                                                    self.active_call(),
4629                                                    &self.active_pane,
4630                                                    self.zoomed.as_ref(),
4631                                                    &self.app_state,
4632                                                    cx,
4633                                                ))
4634                                                .when_some(paddings.1, |this, p| {
4635                                                    this.child(p.border_l_1())
4636                                                }),
4637                                        )
4638                                        .children(self.render_dock(
4639                                            DockPosition::Bottom,
4640                                            &self.bottom_dock,
4641                                            cx,
4642                                        )),
4643                                )
4644                                // Right Dock
4645                                .children(self.render_dock(
4646                                    DockPosition::Right,
4647                                    &self.right_dock,
4648                                    cx,
4649                                )),
4650                        )
4651                        .children(self.zoomed.as_ref().and_then(|view| {
4652                            let zoomed_view = view.upgrade()?;
4653                            let div = div()
4654                                .occlude()
4655                                .absolute()
4656                                .overflow_hidden()
4657                                .border_color(colors.border)
4658                                .bg(colors.background)
4659                                .child(zoomed_view)
4660                                .inset_0()
4661                                .shadow_lg();
4662
4663                            Some(match self.zoomed_position {
4664                                Some(DockPosition::Left) => div.right_2().border_r_1(),
4665                                Some(DockPosition::Right) => div.left_2().border_l_1(),
4666                                Some(DockPosition::Bottom) => div.top_2().border_t_1(),
4667                                None => div.top_2().bottom_2().left_2().right_2().border_1(),
4668                            })
4669                        }))
4670                        .child(self.modal_layer.clone())
4671                        .children(self.render_notifications(cx)),
4672                )
4673                .child(self.status_bar.clone())
4674                .children(if self.project.read(cx).is_disconnected() {
4675                    if let Some(render) = self.render_disconnected_overlay.take() {
4676                        let result = render(self, cx);
4677                        self.render_disconnected_overlay = Some(render);
4678                        Some(result)
4679                    } else {
4680                        None
4681                    }
4682                } else {
4683                    None
4684                }),
4685            cx,
4686        )
4687    }
4688}
4689
4690impl WorkspaceStore {
4691    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4692        Self {
4693            workspaces: Default::default(),
4694            _subscriptions: vec![
4695                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4696                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4697            ],
4698            client,
4699        }
4700    }
4701
4702    pub fn update_followers(
4703        &self,
4704        project_id: Option<u64>,
4705        update: proto::update_followers::Variant,
4706        cx: &AppContext,
4707    ) -> Option<()> {
4708        let active_call = ActiveCall::try_global(cx)?;
4709        let room_id = active_call.read(cx).room()?.read(cx).id();
4710        self.client
4711            .send(proto::UpdateFollowers {
4712                room_id,
4713                project_id,
4714                variant: Some(update),
4715            })
4716            .log_err()
4717    }
4718
4719    pub async fn handle_follow(
4720        this: Model<Self>,
4721        envelope: TypedEnvelope<proto::Follow>,
4722        mut cx: AsyncAppContext,
4723    ) -> Result<proto::FollowResponse> {
4724        this.update(&mut cx, |this, cx| {
4725            let follower = Follower {
4726                project_id: envelope.payload.project_id,
4727                peer_id: envelope.original_sender_id()?,
4728            };
4729
4730            let mut response = proto::FollowResponse::default();
4731            this.workspaces.retain(|workspace| {
4732                workspace
4733                    .update(cx, |workspace, cx| {
4734                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4735                        if let Some(active_view) = handler_response.active_view.clone() {
4736                            if workspace.project.read(cx).remote_id() == follower.project_id {
4737                                response.active_view = Some(active_view)
4738                            }
4739                        }
4740                    })
4741                    .is_ok()
4742            });
4743
4744            Ok(response)
4745        })?
4746    }
4747
4748    async fn handle_update_followers(
4749        this: Model<Self>,
4750        envelope: TypedEnvelope<proto::UpdateFollowers>,
4751        mut cx: AsyncAppContext,
4752    ) -> Result<()> {
4753        let leader_id = envelope.original_sender_id()?;
4754        let update = envelope.payload;
4755
4756        this.update(&mut cx, |this, cx| {
4757            this.workspaces.retain(|workspace| {
4758                workspace
4759                    .update(cx, |workspace, cx| {
4760                        let project_id = workspace.project.read(cx).remote_id();
4761                        if update.project_id != project_id && update.project_id.is_some() {
4762                            return;
4763                        }
4764                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4765                    })
4766                    .is_ok()
4767            });
4768            Ok(())
4769        })?
4770    }
4771}
4772
4773impl ViewId {
4774    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4775        Ok(Self {
4776            creator: message
4777                .creator
4778                .ok_or_else(|| anyhow!("creator is missing"))?,
4779            id: message.id,
4780        })
4781    }
4782
4783    pub(crate) fn to_proto(&self) -> proto::ViewId {
4784        proto::ViewId {
4785            creator: Some(self.creator),
4786            id: self.id,
4787        }
4788    }
4789}
4790
4791impl FollowerState {
4792    fn pane(&self) -> &View<Pane> {
4793        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
4794    }
4795}
4796
4797pub trait WorkspaceHandle {
4798    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4799}
4800
4801impl WorkspaceHandle for View<Workspace> {
4802    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4803        self.read(cx)
4804            .worktrees(cx)
4805            .flat_map(|worktree| {
4806                let worktree_id = worktree.read(cx).id();
4807                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4808                    worktree_id,
4809                    path: f.path.clone(),
4810                })
4811            })
4812            .collect::<Vec<_>>()
4813    }
4814}
4815
4816impl std::fmt::Debug for OpenPaths {
4817    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4818        f.debug_struct("OpenPaths")
4819            .field("paths", &self.paths)
4820            .finish()
4821    }
4822}
4823
4824pub fn activate_workspace_for_project(
4825    cx: &mut AppContext,
4826    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4827) -> Option<WindowHandle<Workspace>> {
4828    for window in cx.windows() {
4829        let Some(workspace) = window.downcast::<Workspace>() else {
4830            continue;
4831        };
4832
4833        let predicate = workspace
4834            .update(cx, |workspace, cx| {
4835                let project = workspace.project.read(cx);
4836                if predicate(project, cx) {
4837                    cx.activate_window();
4838                    true
4839                } else {
4840                    false
4841                }
4842            })
4843            .log_err()
4844            .unwrap_or(false);
4845
4846        if predicate {
4847            return Some(workspace);
4848        }
4849    }
4850
4851    None
4852}
4853
4854pub async fn last_opened_workspace_paths() -> Option<LocalPaths> {
4855    DB.last_workspace().await.log_err().flatten()
4856}
4857
4858actions!(collab, [OpenChannelNotes]);
4859actions!(zed, [OpenLog]);
4860
4861async fn join_channel_internal(
4862    channel_id: ChannelId,
4863    app_state: &Arc<AppState>,
4864    requesting_window: Option<WindowHandle<Workspace>>,
4865    active_call: &Model<ActiveCall>,
4866    cx: &mut AsyncAppContext,
4867) -> Result<bool> {
4868    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4869        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4870            return (false, None);
4871        };
4872
4873        let already_in_channel = room.channel_id() == Some(channel_id);
4874        let should_prompt = room.is_sharing_project()
4875            && room.remote_participants().len() > 0
4876            && !already_in_channel;
4877        let open_room = if already_in_channel {
4878            active_call.room().cloned()
4879        } else {
4880            None
4881        };
4882        (should_prompt, open_room)
4883    })?;
4884
4885    if let Some(room) = open_room {
4886        let task = room.update(cx, |room, cx| {
4887            if let Some((project, host)) = room.most_active_project(cx) {
4888                return Some(join_in_room_project(project, host, app_state.clone(), cx));
4889            }
4890
4891            None
4892        })?;
4893        if let Some(task) = task {
4894            task.await?;
4895        }
4896        return anyhow::Ok(true);
4897    }
4898
4899    if should_prompt {
4900        if let Some(workspace) = requesting_window {
4901            let answer = workspace
4902                .update(cx, |_, cx| {
4903                    cx.prompt(
4904                        PromptLevel::Warning,
4905                        "Do you want to switch channels?",
4906                        Some("Leaving this call will unshare your current project."),
4907                        &["Yes, Join Channel", "Cancel"],
4908                    )
4909                })?
4910                .await;
4911
4912            if answer == Ok(1) {
4913                return Ok(false);
4914            }
4915        } else {
4916            return Ok(false); // unreachable!() hopefully
4917        }
4918    }
4919
4920    let client = cx.update(|cx| active_call.read(cx).client())?;
4921
4922    let mut client_status = client.status();
4923
4924    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4925    'outer: loop {
4926        let Some(status) = client_status.recv().await else {
4927            return Err(anyhow!("error connecting"));
4928        };
4929
4930        match status {
4931            Status::Connecting
4932            | Status::Authenticating
4933            | Status::Reconnecting
4934            | Status::Reauthenticating => continue,
4935            Status::Connected { .. } => break 'outer,
4936            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4937            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4938            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4939                return Err(ErrorCode::Disconnected.into());
4940            }
4941        }
4942    }
4943
4944    let room = active_call
4945        .update(cx, |active_call, cx| {
4946            active_call.join_channel(channel_id, cx)
4947        })?
4948        .await?;
4949
4950    let Some(room) = room else {
4951        return anyhow::Ok(true);
4952    };
4953
4954    room.update(cx, |room, _| room.room_update_completed())?
4955        .await;
4956
4957    let task = room.update(cx, |room, cx| {
4958        if let Some((project, host)) = room.most_active_project(cx) {
4959            return Some(join_in_room_project(project, host, app_state.clone(), cx));
4960        }
4961
4962        // If you are the first to join a channel, see if you should share your project.
4963        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
4964            if let Some(workspace) = requesting_window {
4965                let project = workspace.update(cx, |workspace, cx| {
4966                    let project = workspace.project.read(cx);
4967                    let is_dev_server = project.dev_server_project_id().is_some();
4968
4969                    if !is_dev_server && !CallSettings::get_global(cx).share_on_join {
4970                        return None;
4971                    }
4972
4973                    if (project.is_local() || is_dev_server)
4974                        && project.visible_worktrees(cx).any(|tree| {
4975                            tree.read(cx)
4976                                .root_entry()
4977                                .map_or(false, |entry| entry.is_dir())
4978                        })
4979                    {
4980                        Some(workspace.project.clone())
4981                    } else {
4982                        None
4983                    }
4984                });
4985                if let Ok(Some(project)) = project {
4986                    return Some(cx.spawn(|room, mut cx| async move {
4987                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4988                            .await?;
4989                        Ok(())
4990                    }));
4991                }
4992            }
4993        }
4994
4995        None
4996    })?;
4997    if let Some(task) = task {
4998        task.await?;
4999        return anyhow::Ok(true);
5000    }
5001    anyhow::Ok(false)
5002}
5003
5004pub fn join_channel(
5005    channel_id: ChannelId,
5006    app_state: Arc<AppState>,
5007    requesting_window: Option<WindowHandle<Workspace>>,
5008    cx: &mut AppContext,
5009) -> Task<Result<()>> {
5010    let active_call = ActiveCall::global(cx);
5011    cx.spawn(|mut cx| async move {
5012        let result = join_channel_internal(
5013            channel_id,
5014            &app_state,
5015            requesting_window,
5016            &active_call,
5017            &mut cx,
5018        )
5019            .await;
5020
5021        // join channel succeeded, and opened a window
5022        if matches!(result, Ok(true)) {
5023            return anyhow::Ok(());
5024        }
5025
5026        // find an existing workspace to focus and show call controls
5027        let mut active_window =
5028            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
5029        if active_window.is_none() {
5030            // no open workspaces, make one to show the error in (blergh)
5031            let (window_handle, _) = cx
5032                .update(|cx| {
5033                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
5034                })?
5035                .await?;
5036
5037            if result.is_ok() {
5038                cx.update(|cx| {
5039                    cx.dispatch_action(&OpenChannelNotes);
5040                }).log_err();
5041            }
5042
5043            active_window = Some(window_handle);
5044        }
5045
5046        if let Err(err) = result {
5047            log::error!("failed to join channel: {}", err);
5048            if let Some(active_window) = active_window {
5049                active_window
5050                    .update(&mut cx, |_, cx| {
5051                        let detail: SharedString = match err.error_code() {
5052                            ErrorCode::SignedOut => {
5053                                "Please sign in to continue.".into()
5054                            }
5055                            ErrorCode::UpgradeRequired => {
5056                                "Your are running an unsupported version of Zed. Please update to continue.".into()
5057                            }
5058                            ErrorCode::NoSuchChannel => {
5059                                "No matching channel was found. Please check the link and try again.".into()
5060                            }
5061                            ErrorCode::Forbidden => {
5062                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
5063                            }
5064                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
5065                            _ => format!("{}\n\nPlease try again.", err).into(),
5066                        };
5067                        cx.prompt(
5068                            PromptLevel::Critical,
5069                            "Failed to join channel",
5070                            Some(&detail),
5071                            &["Ok"],
5072                        )
5073                    })?
5074                    .await
5075                    .ok();
5076            }
5077        }
5078
5079        // return ok, we showed the error to the user.
5080        return anyhow::Ok(());
5081    })
5082}
5083
5084pub async fn get_any_active_workspace(
5085    app_state: Arc<AppState>,
5086    mut cx: AsyncAppContext,
5087) -> anyhow::Result<WindowHandle<Workspace>> {
5088    // find an existing workspace to focus and show call controls
5089    let active_window = activate_any_workspace_window(&mut cx);
5090    if active_window.is_none() {
5091        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
5092            .await?;
5093    }
5094    activate_any_workspace_window(&mut cx).context("could not open zed")
5095}
5096
5097fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
5098    cx.update(|cx| {
5099        if let Some(workspace_window) = cx
5100            .active_window()
5101            .and_then(|window| window.downcast::<Workspace>())
5102        {
5103            return Some(workspace_window);
5104        }
5105
5106        for window in cx.windows() {
5107            if let Some(workspace_window) = window.downcast::<Workspace>() {
5108                workspace_window
5109                    .update(cx, |_, cx| cx.activate_window())
5110                    .ok();
5111                return Some(workspace_window);
5112            }
5113        }
5114        None
5115    })
5116    .ok()
5117    .flatten()
5118}
5119
5120fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
5121    cx.windows()
5122        .into_iter()
5123        .filter_map(|window| window.downcast::<Workspace>())
5124        .filter(|workspace| {
5125            workspace
5126                .read(cx)
5127                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
5128        })
5129        .collect()
5130}
5131
5132#[derive(Default)]
5133pub struct OpenOptions {
5134    pub open_new_workspace: Option<bool>,
5135    pub replace_window: Option<WindowHandle<Workspace>>,
5136}
5137
5138#[allow(clippy::type_complexity)]
5139pub fn open_paths(
5140    abs_paths: &[PathBuf],
5141    app_state: Arc<AppState>,
5142    open_options: OpenOptions,
5143    cx: &mut AppContext,
5144) -> Task<
5145    anyhow::Result<(
5146        WindowHandle<Workspace>,
5147        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
5148    )>,
5149> {
5150    let abs_paths = abs_paths.to_vec();
5151    let mut existing = None;
5152    let mut best_match = None;
5153    let mut open_visible = OpenVisible::All;
5154
5155    if open_options.open_new_workspace != Some(true) {
5156        for window in local_workspace_windows(cx) {
5157            if let Ok(workspace) = window.read(cx) {
5158                let m = workspace
5159                    .project
5160                    .read(cx)
5161                    .visibility_for_paths(&abs_paths, cx);
5162                if m > best_match {
5163                    existing = Some(window);
5164                    best_match = m;
5165                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
5166                    existing = Some(window)
5167                }
5168            }
5169        }
5170    }
5171
5172    cx.spawn(move |mut cx| async move {
5173        if open_options.open_new_workspace.is_none() && existing.is_none() {
5174            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
5175            if futures::future::join_all(all_files)
5176                .await
5177                .into_iter()
5178                .filter_map(|result| result.ok().flatten())
5179                .all(|file| !file.is_dir)
5180            {
5181                cx.update(|cx| {
5182                    for window in local_workspace_windows(cx) {
5183                        if let Ok(workspace) = window.read(cx) {
5184                            let project = workspace.project().read(cx);
5185                            if project.is_remote() {
5186                                continue;
5187                            }
5188                            existing = Some(window);
5189                            open_visible = OpenVisible::None;
5190                            break;
5191                        }
5192                    }
5193                })?;
5194            }
5195        }
5196
5197        if let Some(existing) = existing {
5198            Ok((
5199                existing,
5200                existing
5201                    .update(&mut cx, |workspace, cx| {
5202                        cx.activate_window();
5203                        workspace.open_paths(abs_paths, open_visible, None, cx)
5204                    })?
5205                    .await,
5206            ))
5207        } else {
5208            cx.update(move |cx| {
5209                Workspace::new_local(
5210                    abs_paths,
5211                    app_state.clone(),
5212                    open_options.replace_window,
5213                    cx,
5214                )
5215            })?
5216            .await
5217        }
5218    })
5219}
5220
5221pub fn open_new(
5222    app_state: Arc<AppState>,
5223    cx: &mut AppContext,
5224    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
5225) -> Task<anyhow::Result<()>> {
5226    let task = Workspace::new_local(Vec::new(), app_state, None, cx);
5227    cx.spawn(|mut cx| async move {
5228        let (workspace, opened_paths) = task.await?;
5229        workspace.update(&mut cx, |workspace, cx| {
5230            if opened_paths.is_empty() {
5231                init(workspace, cx)
5232            }
5233        })?;
5234        Ok(())
5235    })
5236}
5237
5238pub fn create_and_open_local_file(
5239    path: &'static Path,
5240    cx: &mut ViewContext<Workspace>,
5241    default_content: impl 'static + Send + FnOnce() -> Rope,
5242) -> Task<Result<Box<dyn ItemHandle>>> {
5243    cx.spawn(|workspace, mut cx| async move {
5244        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
5245        if !fs.is_file(path).await {
5246            fs.create_file(path, Default::default()).await?;
5247            fs.save(path, &default_content(), Default::default())
5248                .await?;
5249        }
5250
5251        let mut items = workspace
5252            .update(&mut cx, |workspace, cx| {
5253                workspace.with_local_workspace(cx, |workspace, cx| {
5254                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
5255                })
5256            })?
5257            .await?
5258            .await;
5259
5260        let item = items.pop().flatten();
5261        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
5262    })
5263}
5264
5265pub fn join_hosted_project(
5266    hosted_project_id: ProjectId,
5267    app_state: Arc<AppState>,
5268    cx: &mut AppContext,
5269) -> Task<Result<()>> {
5270    cx.spawn(|mut cx| async move {
5271        let existing_window = cx.update(|cx| {
5272            cx.windows().into_iter().find_map(|window| {
5273                let workspace = window.downcast::<Workspace>()?;
5274                workspace
5275                    .read(cx)
5276                    .is_ok_and(|workspace| {
5277                        workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
5278                    })
5279                    .then(|| workspace)
5280            })
5281        })?;
5282
5283        let workspace = if let Some(existing_window) = existing_window {
5284            existing_window
5285        } else {
5286            let project = Project::hosted(
5287                hosted_project_id,
5288                app_state.user_store.clone(),
5289                app_state.client.clone(),
5290                app_state.languages.clone(),
5291                app_state.fs.clone(),
5292                cx.clone(),
5293            )
5294            .await?;
5295
5296            let window_bounds_override = window_bounds_env_override();
5297            cx.update(|cx| {
5298                let mut options = (app_state.build_window_options)(None, cx);
5299                options.window_bounds =
5300                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5301                cx.open_window(options, |cx| {
5302                    cx.new_view(|cx| {
5303                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5304                    })
5305                })
5306            })??
5307        };
5308
5309        workspace.update(&mut cx, |_, cx| {
5310            cx.activate(true);
5311            cx.activate_window();
5312        })?;
5313
5314        Ok(())
5315    })
5316}
5317
5318pub fn join_dev_server_project(
5319    dev_server_project_id: DevServerProjectId,
5320    project_id: ProjectId,
5321    app_state: Arc<AppState>,
5322    window_to_replace: Option<WindowHandle<Workspace>>,
5323    cx: &mut AppContext,
5324) -> Task<Result<WindowHandle<Workspace>>> {
5325    let windows = cx.windows();
5326    cx.spawn(|mut cx| async move {
5327        let existing_workspace = windows.into_iter().find_map(|window| {
5328            window.downcast::<Workspace>().and_then(|window| {
5329                window
5330                    .update(&mut cx, |workspace, cx| {
5331                        if workspace.project().read(cx).remote_id() == Some(project_id.0) {
5332                            Some(window)
5333                        } else {
5334                            None
5335                        }
5336                    })
5337                    .unwrap_or(None)
5338            })
5339        });
5340
5341        let workspace = if let Some(existing_workspace) = existing_workspace {
5342            existing_workspace
5343        } else {
5344            let project = Project::remote(
5345                project_id.0,
5346                app_state.client.clone(),
5347                app_state.user_store.clone(),
5348                app_state.languages.clone(),
5349                app_state.fs.clone(),
5350                cx.clone(),
5351            )
5352            .await?;
5353
5354            let serialized_workspace: Option<SerializedWorkspace> =
5355                persistence::DB.workspace_for_dev_server_project(dev_server_project_id);
5356
5357            let workspace_id = if let Some(serialized_workspace) = serialized_workspace {
5358                serialized_workspace.id
5359            } else {
5360                persistence::DB.next_id().await?
5361            };
5362
5363            if let Some(window_to_replace) = window_to_replace {
5364                cx.update_window(window_to_replace.into(), |_, cx| {
5365                    cx.replace_root_view(|cx| {
5366                        Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5367                    });
5368                })?;
5369                window_to_replace
5370            } else {
5371                let window_bounds_override = window_bounds_env_override();
5372                cx.update(|cx| {
5373                    let mut options = (app_state.build_window_options)(None, cx);
5374                    options.window_bounds =
5375                        window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5376                    cx.open_window(options, |cx| {
5377                        cx.new_view(|cx| {
5378                            Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5379                        })
5380                    })
5381                })??
5382            }
5383        };
5384
5385        workspace.update(&mut cx, |_, cx| {
5386            cx.activate(true);
5387            cx.activate_window();
5388        })?;
5389
5390        anyhow::Ok(workspace)
5391    })
5392}
5393
5394pub fn join_in_room_project(
5395    project_id: u64,
5396    follow_user_id: u64,
5397    app_state: Arc<AppState>,
5398    cx: &mut AppContext,
5399) -> Task<Result<()>> {
5400    let windows = cx.windows();
5401    cx.spawn(|mut cx| async move {
5402        let existing_workspace = windows.into_iter().find_map(|window| {
5403            window.downcast::<Workspace>().and_then(|window| {
5404                window
5405                    .update(&mut cx, |workspace, cx| {
5406                        if workspace.project().read(cx).remote_id() == Some(project_id) {
5407                            Some(window)
5408                        } else {
5409                            None
5410                        }
5411                    })
5412                    .unwrap_or(None)
5413            })
5414        });
5415
5416        let workspace = if let Some(existing_workspace) = existing_workspace {
5417            existing_workspace
5418        } else {
5419            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
5420            let room = active_call
5421                .read_with(&cx, |call, _| call.room().cloned())?
5422                .ok_or_else(|| anyhow!("not in a call"))?;
5423            let project = room
5424                .update(&mut cx, |room, cx| {
5425                    room.join_project(
5426                        project_id,
5427                        app_state.languages.clone(),
5428                        app_state.fs.clone(),
5429                        cx,
5430                    )
5431                })?
5432                .await?;
5433
5434            let window_bounds_override = window_bounds_env_override();
5435            cx.update(|cx| {
5436                let mut options = (app_state.build_window_options)(None, cx);
5437                options.window_bounds =
5438                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5439                cx.open_window(options, |cx| {
5440                    cx.new_view(|cx| {
5441                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5442                    })
5443                })
5444            })??
5445        };
5446
5447        workspace.update(&mut cx, |workspace, cx| {
5448            cx.activate(true);
5449            cx.activate_window();
5450
5451            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5452                let follow_peer_id = room
5453                    .read(cx)
5454                    .remote_participants()
5455                    .iter()
5456                    .find(|(_, participant)| participant.user.id == follow_user_id)
5457                    .map(|(_, p)| p.peer_id)
5458                    .or_else(|| {
5459                        // If we couldn't follow the given user, follow the host instead.
5460                        let collaborator = workspace
5461                            .project()
5462                            .read(cx)
5463                            .collaborators()
5464                            .values()
5465                            .find(|collaborator| collaborator.replica_id == 0)?;
5466                        Some(collaborator.peer_id)
5467                    });
5468
5469                if let Some(follow_peer_id) = follow_peer_id {
5470                    workspace.follow(follow_peer_id, cx);
5471                }
5472            }
5473        })?;
5474
5475        anyhow::Ok(())
5476    })
5477}
5478
5479pub fn reload(reload: &Reload, cx: &mut AppContext) {
5480    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5481    let mut workspace_windows = cx
5482        .windows()
5483        .into_iter()
5484        .filter_map(|window| window.downcast::<Workspace>())
5485        .collect::<Vec<_>>();
5486
5487    // If multiple windows have unsaved changes, and need a save prompt,
5488    // prompt in the active window before switching to a different window.
5489    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5490
5491    let mut prompt = None;
5492    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5493        prompt = window
5494            .update(cx, |_, cx| {
5495                cx.prompt(
5496                    PromptLevel::Info,
5497                    "Are you sure you want to restart?",
5498                    None,
5499                    &["Restart", "Cancel"],
5500                )
5501            })
5502            .ok();
5503    }
5504
5505    let binary_path = reload.binary_path.clone();
5506    cx.spawn(|mut cx| async move {
5507        if let Some(prompt) = prompt {
5508            let answer = prompt.await?;
5509            if answer != 0 {
5510                return Ok(());
5511            }
5512        }
5513
5514        // If the user cancels any save prompt, then keep the app open.
5515        for window in workspace_windows {
5516            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5517                workspace.prepare_to_close(true, cx)
5518            }) {
5519                if !should_close.await? {
5520                    return Ok(());
5521                }
5522            }
5523        }
5524
5525        cx.update(|cx| cx.restart(binary_path))
5526    })
5527    .detach_and_log_err(cx);
5528}
5529
5530fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
5531    let mut parts = value.split(',');
5532    let x: usize = parts.next()?.parse().ok()?;
5533    let y: usize = parts.next()?.parse().ok()?;
5534    Some(point(px(x as f32), px(y as f32)))
5535}
5536
5537fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
5538    let mut parts = value.split(',');
5539    let width: usize = parts.next()?.parse().ok()?;
5540    let height: usize = parts.next()?.parse().ok()?;
5541    Some(size(px(width as f32), px(height as f32)))
5542}
5543
5544#[cfg(test)]
5545mod tests {
5546    use std::{cell::RefCell, rc::Rc};
5547
5548    use super::*;
5549    use crate::{
5550        dock::{test::TestPanel, PanelEvent},
5551        item::{
5552            test::{TestItem, TestProjectItem},
5553            ItemEvent,
5554        },
5555    };
5556    use fs::FakeFs;
5557    use gpui::{
5558        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
5559        UpdateGlobal, VisualTestContext,
5560    };
5561    use project::{Project, ProjectEntryId};
5562    use serde_json::json;
5563    use settings::SettingsStore;
5564
5565    #[gpui::test]
5566    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
5567        init_test(cx);
5568
5569        let fs = FakeFs::new(cx.executor());
5570        let project = Project::test(fs, [], cx).await;
5571        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5572
5573        // Adding an item with no ambiguity renders the tab without detail.
5574        let item1 = cx.new_view(|cx| {
5575            let mut item = TestItem::new(cx);
5576            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
5577            item
5578        });
5579        workspace.update(cx, |workspace, cx| {
5580            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5581        });
5582        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
5583
5584        // Adding an item that creates ambiguity increases the level of detail on
5585        // both tabs.
5586        let item2 = cx.new_view(|cx| {
5587            let mut item = TestItem::new(cx);
5588            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5589            item
5590        });
5591        workspace.update(cx, |workspace, cx| {
5592            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5593        });
5594        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5595        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5596
5597        // Adding an item that creates ambiguity increases the level of detail only
5598        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
5599        // we stop at the highest detail available.
5600        let item3 = cx.new_view(|cx| {
5601            let mut item = TestItem::new(cx);
5602            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5603            item
5604        });
5605        workspace.update(cx, |workspace, cx| {
5606            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5607        });
5608        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5609        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5610        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5611    }
5612
5613    #[gpui::test]
5614    async fn test_tracking_active_path(cx: &mut TestAppContext) {
5615        init_test(cx);
5616
5617        let fs = FakeFs::new(cx.executor());
5618        fs.insert_tree(
5619            "/root1",
5620            json!({
5621                "one.txt": "",
5622                "two.txt": "",
5623            }),
5624        )
5625        .await;
5626        fs.insert_tree(
5627            "/root2",
5628            json!({
5629                "three.txt": "",
5630            }),
5631        )
5632        .await;
5633
5634        let project = Project::test(fs, ["root1".as_ref()], cx).await;
5635        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5636        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5637        let worktree_id = project.update(cx, |project, cx| {
5638            project.worktrees().next().unwrap().read(cx).id()
5639        });
5640
5641        let item1 = cx.new_view(|cx| {
5642            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
5643        });
5644        let item2 = cx.new_view(|cx| {
5645            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
5646        });
5647
5648        // Add an item to an empty pane
5649        workspace.update(cx, |workspace, cx| {
5650            workspace.add_item_to_active_pane(Box::new(item1), None, cx)
5651        });
5652        project.update(cx, |project, cx| {
5653            assert_eq!(
5654                project.active_entry(),
5655                project
5656                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5657                    .map(|e| e.id)
5658            );
5659        });
5660        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5661
5662        // Add a second item to a non-empty pane
5663        workspace.update(cx, |workspace, cx| {
5664            workspace.add_item_to_active_pane(Box::new(item2), None, cx)
5665        });
5666        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
5667        project.update(cx, |project, cx| {
5668            assert_eq!(
5669                project.active_entry(),
5670                project
5671                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
5672                    .map(|e| e.id)
5673            );
5674        });
5675
5676        // Close the active item
5677        pane.update(cx, |pane, cx| {
5678            pane.close_active_item(&Default::default(), cx).unwrap()
5679        })
5680        .await
5681        .unwrap();
5682        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5683        project.update(cx, |project, cx| {
5684            assert_eq!(
5685                project.active_entry(),
5686                project
5687                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5688                    .map(|e| e.id)
5689            );
5690        });
5691
5692        // Add a project folder
5693        project
5694            .update(cx, |project, cx| {
5695                project.find_or_create_worktree("root2", true, cx)
5696            })
5697            .await
5698            .unwrap();
5699        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
5700
5701        // Remove a project folder
5702        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
5703        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
5704    }
5705
5706    #[gpui::test]
5707    async fn test_close_window(cx: &mut TestAppContext) {
5708        init_test(cx);
5709
5710        let fs = FakeFs::new(cx.executor());
5711        fs.insert_tree("/root", json!({ "one": "" })).await;
5712
5713        let project = Project::test(fs, ["root".as_ref()], cx).await;
5714        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5715
5716        // When there are no dirty items, there's nothing to do.
5717        let item1 = cx.new_view(|cx| TestItem::new(cx));
5718        workspace.update(cx, |w, cx| {
5719            w.add_item_to_active_pane(Box::new(item1.clone()), None, cx)
5720        });
5721        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5722        assert!(task.await.unwrap());
5723
5724        // When there are dirty untitled items, prompt to save each one. If the user
5725        // cancels any prompt, then abort.
5726        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
5727        let item3 = cx.new_view(|cx| {
5728            TestItem::new(cx)
5729                .with_dirty(true)
5730                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5731        });
5732        workspace.update(cx, |w, cx| {
5733            w.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5734            w.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5735        });
5736        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5737        cx.executor().run_until_parked();
5738        cx.simulate_prompt_answer(2); // cancel save all
5739        cx.executor().run_until_parked();
5740        cx.simulate_prompt_answer(2); // cancel save all
5741        cx.executor().run_until_parked();
5742        assert!(!cx.has_pending_prompt());
5743        assert!(!task.await.unwrap());
5744    }
5745
5746    #[gpui::test]
5747    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
5748        init_test(cx);
5749
5750        // Register TestItem as a serializable item
5751        cx.update(|cx| {
5752            register_serializable_item::<TestItem>(cx);
5753        });
5754
5755        let fs = FakeFs::new(cx.executor());
5756        fs.insert_tree("/root", json!({ "one": "" })).await;
5757
5758        let project = Project::test(fs, ["root".as_ref()], cx).await;
5759        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5760
5761        // When there are dirty untitled items, but they can serialize, then there is no prompt.
5762        let item1 = cx.new_view(|cx| {
5763            TestItem::new(cx)
5764                .with_dirty(true)
5765                .with_serialize(|| Some(Task::ready(Ok(()))))
5766        });
5767        let item2 = cx.new_view(|cx| {
5768            TestItem::new(cx)
5769                .with_dirty(true)
5770                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5771                .with_serialize(|| Some(Task::ready(Ok(()))))
5772        });
5773        workspace.update(cx, |w, cx| {
5774            w.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5775            w.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5776        });
5777        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5778        assert!(task.await.unwrap());
5779    }
5780
5781    #[gpui::test]
5782    async fn test_close_pane_items(cx: &mut TestAppContext) {
5783        init_test(cx);
5784
5785        let fs = FakeFs::new(cx.executor());
5786
5787        let project = Project::test(fs, None, cx).await;
5788        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5789
5790        let item1 = cx.new_view(|cx| {
5791            TestItem::new(cx)
5792                .with_dirty(true)
5793                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5794        });
5795        let item2 = cx.new_view(|cx| {
5796            TestItem::new(cx)
5797                .with_dirty(true)
5798                .with_conflict(true)
5799                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
5800        });
5801        let item3 = cx.new_view(|cx| {
5802            TestItem::new(cx)
5803                .with_dirty(true)
5804                .with_conflict(true)
5805                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
5806        });
5807        let item4 = cx.new_view(|cx| {
5808            TestItem::new(cx)
5809                .with_dirty(true)
5810                .with_project_items(&[TestProjectItem::new_untitled(cx)])
5811        });
5812        let pane = workspace.update(cx, |workspace, cx| {
5813            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5814            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5815            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5816            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, cx);
5817            workspace.active_pane().clone()
5818        });
5819
5820        let close_items = pane.update(cx, |pane, cx| {
5821            pane.activate_item(1, true, true, cx);
5822            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5823            let item1_id = item1.item_id();
5824            let item3_id = item3.item_id();
5825            let item4_id = item4.item_id();
5826            pane.close_items(cx, SaveIntent::Close, move |id| {
5827                [item1_id, item3_id, item4_id].contains(&id)
5828            })
5829        });
5830        cx.executor().run_until_parked();
5831
5832        assert!(cx.has_pending_prompt());
5833        // Ignore "Save all" prompt
5834        cx.simulate_prompt_answer(2);
5835        cx.executor().run_until_parked();
5836        // There's a prompt to save item 1.
5837        pane.update(cx, |pane, _| {
5838            assert_eq!(pane.items_len(), 4);
5839            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
5840        });
5841        // Confirm saving item 1.
5842        cx.simulate_prompt_answer(0);
5843        cx.executor().run_until_parked();
5844
5845        // Item 1 is saved. There's a prompt to save item 3.
5846        pane.update(cx, |pane, cx| {
5847            assert_eq!(item1.read(cx).save_count, 1);
5848            assert_eq!(item1.read(cx).save_as_count, 0);
5849            assert_eq!(item1.read(cx).reload_count, 0);
5850            assert_eq!(pane.items_len(), 3);
5851            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
5852        });
5853        assert!(cx.has_pending_prompt());
5854
5855        // Cancel saving item 3.
5856        cx.simulate_prompt_answer(1);
5857        cx.executor().run_until_parked();
5858
5859        // Item 3 is reloaded. There's a prompt to save item 4.
5860        pane.update(cx, |pane, cx| {
5861            assert_eq!(item3.read(cx).save_count, 0);
5862            assert_eq!(item3.read(cx).save_as_count, 0);
5863            assert_eq!(item3.read(cx).reload_count, 1);
5864            assert_eq!(pane.items_len(), 2);
5865            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
5866        });
5867        assert!(cx.has_pending_prompt());
5868
5869        // Confirm saving item 4.
5870        cx.simulate_prompt_answer(0);
5871        cx.executor().run_until_parked();
5872
5873        // There's a prompt for a path for item 4.
5874        cx.simulate_new_path_selection(|_| Some(Default::default()));
5875        close_items.await.unwrap();
5876
5877        // The requested items are closed.
5878        pane.update(cx, |pane, cx| {
5879            assert_eq!(item4.read(cx).save_count, 0);
5880            assert_eq!(item4.read(cx).save_as_count, 1);
5881            assert_eq!(item4.read(cx).reload_count, 0);
5882            assert_eq!(pane.items_len(), 1);
5883            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5884        });
5885    }
5886
5887    #[gpui::test]
5888    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
5889        init_test(cx);
5890
5891        let fs = FakeFs::new(cx.executor());
5892        let project = Project::test(fs, [], cx).await;
5893        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5894
5895        // Create several workspace items with single project entries, and two
5896        // workspace items with multiple project entries.
5897        let single_entry_items = (0..=4)
5898            .map(|project_entry_id| {
5899                cx.new_view(|cx| {
5900                    TestItem::new(cx)
5901                        .with_dirty(true)
5902                        .with_project_items(&[TestProjectItem::new(
5903                            project_entry_id,
5904                            &format!("{project_entry_id}.txt"),
5905                            cx,
5906                        )])
5907                })
5908            })
5909            .collect::<Vec<_>>();
5910        let item_2_3 = cx.new_view(|cx| {
5911            TestItem::new(cx)
5912                .with_dirty(true)
5913                .with_singleton(false)
5914                .with_project_items(&[
5915                    single_entry_items[2].read(cx).project_items[0].clone(),
5916                    single_entry_items[3].read(cx).project_items[0].clone(),
5917                ])
5918        });
5919        let item_3_4 = cx.new_view(|cx| {
5920            TestItem::new(cx)
5921                .with_dirty(true)
5922                .with_singleton(false)
5923                .with_project_items(&[
5924                    single_entry_items[3].read(cx).project_items[0].clone(),
5925                    single_entry_items[4].read(cx).project_items[0].clone(),
5926                ])
5927        });
5928
5929        // Create two panes that contain the following project entries:
5930        //   left pane:
5931        //     multi-entry items:   (2, 3)
5932        //     single-entry items:  0, 1, 2, 3, 4
5933        //   right pane:
5934        //     single-entry items:  1
5935        //     multi-entry items:   (3, 4)
5936        let left_pane = workspace.update(cx, |workspace, cx| {
5937            let left_pane = workspace.active_pane().clone();
5938            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, cx);
5939            for item in single_entry_items {
5940                workspace.add_item_to_active_pane(Box::new(item), None, cx);
5941            }
5942            left_pane.update(cx, |pane, cx| {
5943                pane.activate_item(2, true, true, cx);
5944            });
5945
5946            let right_pane = workspace
5947                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5948                .unwrap();
5949
5950            right_pane.update(cx, |pane, cx| {
5951                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5952            });
5953
5954            left_pane
5955        });
5956
5957        cx.focus_view(&left_pane);
5958
5959        // When closing all of the items in the left pane, we should be prompted twice:
5960        // once for project entry 0, and once for project entry 2. Project entries 1,
5961        // 3, and 4 are all still open in the other paten. After those two
5962        // prompts, the task should complete.
5963
5964        let close = left_pane.update(cx, |pane, cx| {
5965            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5966        });
5967        cx.executor().run_until_parked();
5968
5969        // Discard "Save all" prompt
5970        cx.simulate_prompt_answer(2);
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(0)]
5977            );
5978        });
5979        cx.simulate_prompt_answer(0);
5980
5981        cx.executor().run_until_parked();
5982        left_pane.update(cx, |pane, cx| {
5983            assert_eq!(
5984                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5985                &[ProjectEntryId::from_proto(2)]
5986            );
5987        });
5988        cx.simulate_prompt_answer(0);
5989
5990        cx.executor().run_until_parked();
5991        close.await.unwrap();
5992        left_pane.update(cx, |pane, _| {
5993            assert_eq!(pane.items_len(), 0);
5994        });
5995    }
5996
5997    #[gpui::test]
5998    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5999        init_test(cx);
6000
6001        let fs = FakeFs::new(cx.executor());
6002        let project = Project::test(fs, [], cx).await;
6003        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6004        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6005
6006        let item = cx.new_view(|cx| {
6007            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6008        });
6009        let item_id = item.entity_id();
6010        workspace.update(cx, |workspace, cx| {
6011            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
6012        });
6013
6014        // Autosave on window change.
6015        item.update(cx, |item, cx| {
6016            SettingsStore::update_global(cx, |settings, cx| {
6017                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6018                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
6019                })
6020            });
6021            item.is_dirty = true;
6022        });
6023
6024        // Deactivating the window saves the file.
6025        cx.deactivate_window();
6026        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6027
6028        // Re-activating the window doesn't save the file.
6029        cx.update(|cx| cx.activate_window());
6030        cx.executor().run_until_parked();
6031        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6032
6033        // Autosave on focus change.
6034        item.update(cx, |item, cx| {
6035            cx.focus_self();
6036            SettingsStore::update_global(cx, |settings, cx| {
6037                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6038                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6039                })
6040            });
6041            item.is_dirty = true;
6042        });
6043
6044        // Blurring the item saves the file.
6045        item.update(cx, |_, cx| cx.blur());
6046        cx.executor().run_until_parked();
6047        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
6048
6049        // Deactivating the window still saves the file.
6050        item.update(cx, |item, cx| {
6051            cx.focus_self();
6052            item.is_dirty = true;
6053        });
6054        cx.deactivate_window();
6055        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6056
6057        // Autosave after delay.
6058        item.update(cx, |item, cx| {
6059            SettingsStore::update_global(cx, |settings, cx| {
6060                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6061                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
6062                })
6063            });
6064            item.is_dirty = true;
6065            cx.emit(ItemEvent::Edit);
6066        });
6067
6068        // Delay hasn't fully expired, so the file is still dirty and unsaved.
6069        cx.executor().advance_clock(Duration::from_millis(250));
6070        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6071
6072        // After delay expires, the file is saved.
6073        cx.executor().advance_clock(Duration::from_millis(250));
6074        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
6075
6076        // Autosave on focus change, ensuring closing the tab counts as such.
6077        item.update(cx, |item, cx| {
6078            SettingsStore::update_global(cx, |settings, cx| {
6079                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6080                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6081                })
6082            });
6083            item.is_dirty = true;
6084        });
6085
6086        pane.update(cx, |pane, cx| {
6087            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6088        })
6089        .await
6090        .unwrap();
6091        assert!(!cx.has_pending_prompt());
6092        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6093
6094        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
6095        workspace.update(cx, |workspace, cx| {
6096            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
6097        });
6098        item.update(cx, |item, cx| {
6099            item.project_items[0].update(cx, |item, _| {
6100                item.entry_id = None;
6101            });
6102            item.is_dirty = true;
6103            cx.blur();
6104        });
6105        cx.run_until_parked();
6106        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6107
6108        // Ensure autosave is prevented for deleted files also when closing the buffer.
6109        let _close_items = pane.update(cx, |pane, cx| {
6110            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6111        });
6112        cx.run_until_parked();
6113        assert!(cx.has_pending_prompt());
6114        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6115    }
6116
6117    #[gpui::test]
6118    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
6119        init_test(cx);
6120
6121        let fs = FakeFs::new(cx.executor());
6122
6123        let project = Project::test(fs, [], cx).await;
6124        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6125
6126        let item = cx.new_view(|cx| {
6127            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6128        });
6129        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6130        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
6131        let toolbar_notify_count = Rc::new(RefCell::new(0));
6132
6133        workspace.update(cx, |workspace, cx| {
6134            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
6135            let toolbar_notification_count = toolbar_notify_count.clone();
6136            cx.observe(&toolbar, move |_, _, _| {
6137                *toolbar_notification_count.borrow_mut() += 1
6138            })
6139            .detach();
6140        });
6141
6142        pane.update(cx, |pane, _| {
6143            assert!(!pane.can_navigate_backward());
6144            assert!(!pane.can_navigate_forward());
6145        });
6146
6147        item.update(cx, |item, cx| {
6148            item.set_state("one".to_string(), cx);
6149        });
6150
6151        // Toolbar must be notified to re-render the navigation buttons
6152        assert_eq!(*toolbar_notify_count.borrow(), 1);
6153
6154        pane.update(cx, |pane, _| {
6155            assert!(pane.can_navigate_backward());
6156            assert!(!pane.can_navigate_forward());
6157        });
6158
6159        workspace
6160            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
6161            .await
6162            .unwrap();
6163
6164        assert_eq!(*toolbar_notify_count.borrow(), 2);
6165        pane.update(cx, |pane, _| {
6166            assert!(!pane.can_navigate_backward());
6167            assert!(pane.can_navigate_forward());
6168        });
6169    }
6170
6171    #[gpui::test]
6172    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
6173        init_test(cx);
6174        let fs = FakeFs::new(cx.executor());
6175
6176        let project = Project::test(fs, [], cx).await;
6177        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6178
6179        let panel = workspace.update(cx, |workspace, cx| {
6180            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6181            workspace.add_panel(panel.clone(), cx);
6182
6183            workspace
6184                .right_dock()
6185                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6186
6187            panel
6188        });
6189
6190        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6191        pane.update(cx, |pane, cx| {
6192            let item = cx.new_view(|cx| TestItem::new(cx));
6193            pane.add_item(Box::new(item), true, true, None, cx);
6194        });
6195
6196        // Transfer focus from center to panel
6197        workspace.update(cx, |workspace, cx| {
6198            workspace.toggle_panel_focus::<TestPanel>(cx);
6199        });
6200
6201        workspace.update(cx, |workspace, cx| {
6202            assert!(workspace.right_dock().read(cx).is_open());
6203            assert!(!panel.is_zoomed(cx));
6204            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6205        });
6206
6207        // Transfer focus from panel to center
6208        workspace.update(cx, |workspace, cx| {
6209            workspace.toggle_panel_focus::<TestPanel>(cx);
6210        });
6211
6212        workspace.update(cx, |workspace, cx| {
6213            assert!(workspace.right_dock().read(cx).is_open());
6214            assert!(!panel.is_zoomed(cx));
6215            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6216        });
6217
6218        // Close the dock
6219        workspace.update(cx, |workspace, cx| {
6220            workspace.toggle_dock(DockPosition::Right, cx);
6221        });
6222
6223        workspace.update(cx, |workspace, cx| {
6224            assert!(!workspace.right_dock().read(cx).is_open());
6225            assert!(!panel.is_zoomed(cx));
6226            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6227        });
6228
6229        // Open the dock
6230        workspace.update(cx, |workspace, cx| {
6231            workspace.toggle_dock(DockPosition::Right, cx);
6232        });
6233
6234        workspace.update(cx, |workspace, cx| {
6235            assert!(workspace.right_dock().read(cx).is_open());
6236            assert!(!panel.is_zoomed(cx));
6237            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6238        });
6239
6240        // Focus and zoom panel
6241        panel.update(cx, |panel, cx| {
6242            cx.focus_self();
6243            panel.set_zoomed(true, cx)
6244        });
6245
6246        workspace.update(cx, |workspace, cx| {
6247            assert!(workspace.right_dock().read(cx).is_open());
6248            assert!(panel.is_zoomed(cx));
6249            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6250        });
6251
6252        // Transfer focus to the center closes the dock
6253        workspace.update(cx, |workspace, cx| {
6254            workspace.toggle_panel_focus::<TestPanel>(cx);
6255        });
6256
6257        workspace.update(cx, |workspace, cx| {
6258            assert!(!workspace.right_dock().read(cx).is_open());
6259            assert!(panel.is_zoomed(cx));
6260            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6261        });
6262
6263        // Transferring focus back to the panel keeps it zoomed
6264        workspace.update(cx, |workspace, cx| {
6265            workspace.toggle_panel_focus::<TestPanel>(cx);
6266        });
6267
6268        workspace.update(cx, |workspace, cx| {
6269            assert!(workspace.right_dock().read(cx).is_open());
6270            assert!(panel.is_zoomed(cx));
6271            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6272        });
6273
6274        // Close the dock while it is zoomed
6275        workspace.update(cx, |workspace, cx| {
6276            workspace.toggle_dock(DockPosition::Right, cx)
6277        });
6278
6279        workspace.update(cx, |workspace, cx| {
6280            assert!(!workspace.right_dock().read(cx).is_open());
6281            assert!(panel.is_zoomed(cx));
6282            assert!(workspace.zoomed.is_none());
6283            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6284        });
6285
6286        // Opening the dock, when it's zoomed, retains focus
6287        workspace.update(cx, |workspace, cx| {
6288            workspace.toggle_dock(DockPosition::Right, cx)
6289        });
6290
6291        workspace.update(cx, |workspace, cx| {
6292            assert!(workspace.right_dock().read(cx).is_open());
6293            assert!(panel.is_zoomed(cx));
6294            assert!(workspace.zoomed.is_some());
6295            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6296        });
6297
6298        // Unzoom and close the panel, zoom the active pane.
6299        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
6300        workspace.update(cx, |workspace, cx| {
6301            workspace.toggle_dock(DockPosition::Right, cx)
6302        });
6303        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
6304
6305        // Opening a dock unzooms the pane.
6306        workspace.update(cx, |workspace, cx| {
6307            workspace.toggle_dock(DockPosition::Right, cx)
6308        });
6309        workspace.update(cx, |workspace, cx| {
6310            let pane = pane.read(cx);
6311            assert!(!pane.is_zoomed());
6312            assert!(!pane.focus_handle(cx).is_focused(cx));
6313            assert!(workspace.right_dock().read(cx).is_open());
6314            assert!(workspace.zoomed.is_none());
6315        });
6316    }
6317
6318    struct TestModal(FocusHandle);
6319
6320    impl TestModal {
6321        fn new(cx: &mut ViewContext<Self>) -> Self {
6322            Self(cx.focus_handle())
6323        }
6324    }
6325
6326    impl EventEmitter<DismissEvent> for TestModal {}
6327
6328    impl FocusableView for TestModal {
6329        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6330            self.0.clone()
6331        }
6332    }
6333
6334    impl ModalView for TestModal {}
6335
6336    impl Render for TestModal {
6337        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
6338            div().track_focus(&self.0)
6339        }
6340    }
6341
6342    #[gpui::test]
6343    async fn test_panels(cx: &mut gpui::TestAppContext) {
6344        init_test(cx);
6345        let fs = FakeFs::new(cx.executor());
6346
6347        let project = Project::test(fs, [], cx).await;
6348        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6349
6350        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
6351            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
6352            workspace.add_panel(panel_1.clone(), cx);
6353            workspace
6354                .left_dock()
6355                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
6356            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6357            workspace.add_panel(panel_2.clone(), cx);
6358            workspace
6359                .right_dock()
6360                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6361
6362            let left_dock = workspace.left_dock();
6363            assert_eq!(
6364                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6365                panel_1.panel_id()
6366            );
6367            assert_eq!(
6368                left_dock.read(cx).active_panel_size(cx).unwrap(),
6369                panel_1.size(cx)
6370            );
6371
6372            left_dock.update(cx, |left_dock, cx| {
6373                left_dock.resize_active_panel(Some(px(1337.)), cx)
6374            });
6375            assert_eq!(
6376                workspace
6377                    .right_dock()
6378                    .read(cx)
6379                    .visible_panel()
6380                    .unwrap()
6381                    .panel_id(),
6382                panel_2.panel_id(),
6383            );
6384
6385            (panel_1, panel_2)
6386        });
6387
6388        // Move panel_1 to the right
6389        panel_1.update(cx, |panel_1, cx| {
6390            panel_1.set_position(DockPosition::Right, cx)
6391        });
6392
6393        workspace.update(cx, |workspace, cx| {
6394            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
6395            // Since it was the only panel on the left, the left dock should now be closed.
6396            assert!(!workspace.left_dock().read(cx).is_open());
6397            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
6398            let right_dock = workspace.right_dock();
6399            assert_eq!(
6400                right_dock.read(cx).visible_panel().unwrap().panel_id(),
6401                panel_1.panel_id()
6402            );
6403            assert_eq!(
6404                right_dock.read(cx).active_panel_size(cx).unwrap(),
6405                px(1337.)
6406            );
6407
6408            // Now we move panel_2 to the left
6409            panel_2.set_position(DockPosition::Left, cx);
6410        });
6411
6412        workspace.update(cx, |workspace, cx| {
6413            // Since panel_2 was not visible on the right, we don't open the left dock.
6414            assert!(!workspace.left_dock().read(cx).is_open());
6415            // And the right dock is unaffected in its displaying of panel_1
6416            assert!(workspace.right_dock().read(cx).is_open());
6417            assert_eq!(
6418                workspace
6419                    .right_dock()
6420                    .read(cx)
6421                    .visible_panel()
6422                    .unwrap()
6423                    .panel_id(),
6424                panel_1.panel_id(),
6425            );
6426        });
6427
6428        // Move panel_1 back to the left
6429        panel_1.update(cx, |panel_1, cx| {
6430            panel_1.set_position(DockPosition::Left, cx)
6431        });
6432
6433        workspace.update(cx, |workspace, cx| {
6434            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
6435            let left_dock = workspace.left_dock();
6436            assert!(left_dock.read(cx).is_open());
6437            assert_eq!(
6438                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6439                panel_1.panel_id()
6440            );
6441            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
6442            // And the right dock should be closed as it no longer has any panels.
6443            assert!(!workspace.right_dock().read(cx).is_open());
6444
6445            // Now we move panel_1 to the bottom
6446            panel_1.set_position(DockPosition::Bottom, cx);
6447        });
6448
6449        workspace.update(cx, |workspace, cx| {
6450            // Since panel_1 was visible on the left, we close the left dock.
6451            assert!(!workspace.left_dock().read(cx).is_open());
6452            // The bottom dock is sized based on the panel's default size,
6453            // since the panel orientation changed from vertical to horizontal.
6454            let bottom_dock = workspace.bottom_dock();
6455            assert_eq!(
6456                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
6457                panel_1.size(cx),
6458            );
6459            // Close bottom dock and move panel_1 back to the left.
6460            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
6461            panel_1.set_position(DockPosition::Left, cx);
6462        });
6463
6464        // Emit activated event on panel 1
6465        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
6466
6467        // Now the left dock is open and panel_1 is active and focused.
6468        workspace.update(cx, |workspace, cx| {
6469            let left_dock = workspace.left_dock();
6470            assert!(left_dock.read(cx).is_open());
6471            assert_eq!(
6472                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6473                panel_1.panel_id(),
6474            );
6475            assert!(panel_1.focus_handle(cx).is_focused(cx));
6476        });
6477
6478        // Emit closed event on panel 2, which is not active
6479        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6480
6481        // Wo don't close the left dock, because panel_2 wasn't the active panel
6482        workspace.update(cx, |workspace, cx| {
6483            let left_dock = workspace.left_dock();
6484            assert!(left_dock.read(cx).is_open());
6485            assert_eq!(
6486                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6487                panel_1.panel_id(),
6488            );
6489        });
6490
6491        // Emitting a ZoomIn event shows the panel as zoomed.
6492        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
6493        workspace.update(cx, |workspace, _| {
6494            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6495            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
6496        });
6497
6498        // Move panel to another dock while it is zoomed
6499        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
6500        workspace.update(cx, |workspace, _| {
6501            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6502
6503            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6504        });
6505
6506        // This is a helper for getting a:
6507        // - valid focus on an element,
6508        // - that isn't a part of the panes and panels system of the Workspace,
6509        // - and doesn't trigger the 'on_focus_lost' API.
6510        let focus_other_view = {
6511            let workspace = workspace.clone();
6512            move |cx: &mut VisualTestContext| {
6513                workspace.update(cx, |workspace, cx| {
6514                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
6515                        workspace.toggle_modal(cx, TestModal::new);
6516                        workspace.toggle_modal(cx, TestModal::new);
6517                    } else {
6518                        workspace.toggle_modal(cx, TestModal::new);
6519                    }
6520                })
6521            }
6522        };
6523
6524        // If focus is transferred to another view that's not a panel or another pane, we still show
6525        // the panel as zoomed.
6526        focus_other_view(cx);
6527        workspace.update(cx, |workspace, _| {
6528            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6529            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6530        });
6531
6532        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
6533        workspace.update(cx, |_, cx| cx.focus_self());
6534        workspace.update(cx, |workspace, _| {
6535            assert_eq!(workspace.zoomed, None);
6536            assert_eq!(workspace.zoomed_position, None);
6537        });
6538
6539        // If focus is transferred again to another view that's not a panel or a pane, we won't
6540        // show the panel as zoomed because it wasn't zoomed before.
6541        focus_other_view(cx);
6542        workspace.update(cx, |workspace, _| {
6543            assert_eq!(workspace.zoomed, None);
6544            assert_eq!(workspace.zoomed_position, None);
6545        });
6546
6547        // When the panel is activated, it is zoomed again.
6548        cx.dispatch_action(ToggleRightDock);
6549        workspace.update(cx, |workspace, _| {
6550            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6551            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6552        });
6553
6554        // Emitting a ZoomOut event unzooms the panel.
6555        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
6556        workspace.update(cx, |workspace, _| {
6557            assert_eq!(workspace.zoomed, None);
6558            assert_eq!(workspace.zoomed_position, None);
6559        });
6560
6561        // Emit closed event on panel 1, which is active
6562        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6563
6564        // Now the left dock is closed, because panel_1 was the active panel
6565        workspace.update(cx, |workspace, cx| {
6566            let right_dock = workspace.right_dock();
6567            assert!(!right_dock.read(cx).is_open());
6568        });
6569    }
6570
6571    mod register_project_item_tests {
6572        use ui::Context as _;
6573
6574        use super::*;
6575
6576        // View
6577        struct TestPngItemView {
6578            focus_handle: FocusHandle,
6579        }
6580        // Model
6581        struct TestPngItem {}
6582
6583        impl project::Item for TestPngItem {
6584            fn try_open(
6585                _project: &Model<Project>,
6586                path: &ProjectPath,
6587                cx: &mut AppContext,
6588            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6589                if path.path.extension().unwrap() == "png" {
6590                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
6591                } else {
6592                    None
6593                }
6594            }
6595
6596            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6597                None
6598            }
6599
6600            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6601                None
6602            }
6603        }
6604
6605        impl Item for TestPngItemView {
6606            type Event = ();
6607        }
6608        impl EventEmitter<()> for TestPngItemView {}
6609        impl FocusableView for TestPngItemView {
6610            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6611                self.focus_handle.clone()
6612            }
6613        }
6614
6615        impl Render for TestPngItemView {
6616            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6617                Empty
6618            }
6619        }
6620
6621        impl ProjectItem for TestPngItemView {
6622            type Item = TestPngItem;
6623
6624            fn for_project_item(
6625                _project: Model<Project>,
6626                _item: Model<Self::Item>,
6627                cx: &mut ViewContext<Self>,
6628            ) -> Self
6629            where
6630                Self: Sized,
6631            {
6632                Self {
6633                    focus_handle: cx.focus_handle(),
6634                }
6635            }
6636        }
6637
6638        // View
6639        struct TestIpynbItemView {
6640            focus_handle: FocusHandle,
6641        }
6642        // Model
6643        struct TestIpynbItem {}
6644
6645        impl project::Item for TestIpynbItem {
6646            fn try_open(
6647                _project: &Model<Project>,
6648                path: &ProjectPath,
6649                cx: &mut AppContext,
6650            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6651                if path.path.extension().unwrap() == "ipynb" {
6652                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
6653                } else {
6654                    None
6655                }
6656            }
6657
6658            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6659                None
6660            }
6661
6662            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6663                None
6664            }
6665        }
6666
6667        impl Item for TestIpynbItemView {
6668            type Event = ();
6669        }
6670        impl EventEmitter<()> for TestIpynbItemView {}
6671        impl FocusableView for TestIpynbItemView {
6672            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6673                self.focus_handle.clone()
6674            }
6675        }
6676
6677        impl Render for TestIpynbItemView {
6678            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6679                Empty
6680            }
6681        }
6682
6683        impl ProjectItem for TestIpynbItemView {
6684            type Item = TestIpynbItem;
6685
6686            fn for_project_item(
6687                _project: Model<Project>,
6688                _item: Model<Self::Item>,
6689                cx: &mut ViewContext<Self>,
6690            ) -> Self
6691            where
6692                Self: Sized,
6693            {
6694                Self {
6695                    focus_handle: cx.focus_handle(),
6696                }
6697            }
6698        }
6699
6700        struct TestAlternatePngItemView {
6701            focus_handle: FocusHandle,
6702        }
6703
6704        impl Item for TestAlternatePngItemView {
6705            type Event = ();
6706        }
6707
6708        impl EventEmitter<()> for TestAlternatePngItemView {}
6709        impl FocusableView for TestAlternatePngItemView {
6710            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6711                self.focus_handle.clone()
6712            }
6713        }
6714
6715        impl Render for TestAlternatePngItemView {
6716            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6717                Empty
6718            }
6719        }
6720
6721        impl ProjectItem for TestAlternatePngItemView {
6722            type Item = TestPngItem;
6723
6724            fn for_project_item(
6725                _project: Model<Project>,
6726                _item: Model<Self::Item>,
6727                cx: &mut ViewContext<Self>,
6728            ) -> Self
6729            where
6730                Self: Sized,
6731            {
6732                Self {
6733                    focus_handle: cx.focus_handle(),
6734                }
6735            }
6736        }
6737
6738        #[gpui::test]
6739        async fn test_register_project_item(cx: &mut TestAppContext) {
6740            init_test(cx);
6741
6742            cx.update(|cx| {
6743                register_project_item::<TestPngItemView>(cx);
6744                register_project_item::<TestIpynbItemView>(cx);
6745            });
6746
6747            let fs = FakeFs::new(cx.executor());
6748            fs.insert_tree(
6749                "/root1",
6750                json!({
6751                    "one.png": "BINARYDATAHERE",
6752                    "two.ipynb": "{ totally a notebook }",
6753                    "three.txt": "editing text, sure why not?"
6754                }),
6755            )
6756            .await;
6757
6758            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6759            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6760
6761            let worktree_id = project.update(cx, |project, cx| {
6762                project.worktrees().next().unwrap().read(cx).id()
6763            });
6764
6765            let handle = workspace
6766                .update(cx, |workspace, cx| {
6767                    let project_path = (worktree_id, "one.png");
6768                    workspace.open_path(project_path, None, true, cx)
6769                })
6770                .await
6771                .unwrap();
6772
6773            // Now we can check if the handle we got back errored or not
6774            assert_eq!(
6775                handle.to_any().entity_type(),
6776                TypeId::of::<TestPngItemView>()
6777            );
6778
6779            let handle = workspace
6780                .update(cx, |workspace, cx| {
6781                    let project_path = (worktree_id, "two.ipynb");
6782                    workspace.open_path(project_path, None, true, cx)
6783                })
6784                .await
6785                .unwrap();
6786
6787            assert_eq!(
6788                handle.to_any().entity_type(),
6789                TypeId::of::<TestIpynbItemView>()
6790            );
6791
6792            let handle = workspace
6793                .update(cx, |workspace, cx| {
6794                    let project_path = (worktree_id, "three.txt");
6795                    workspace.open_path(project_path, None, true, cx)
6796                })
6797                .await;
6798            assert!(handle.is_err());
6799        }
6800
6801        #[gpui::test]
6802        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
6803            init_test(cx);
6804
6805            cx.update(|cx| {
6806                register_project_item::<TestPngItemView>(cx);
6807                register_project_item::<TestAlternatePngItemView>(cx);
6808            });
6809
6810            let fs = FakeFs::new(cx.executor());
6811            fs.insert_tree(
6812                "/root1",
6813                json!({
6814                    "one.png": "BINARYDATAHERE",
6815                    "two.ipynb": "{ totally a notebook }",
6816                    "three.txt": "editing text, sure why not?"
6817                }),
6818            )
6819            .await;
6820
6821            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6822            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6823
6824            let worktree_id = project.update(cx, |project, cx| {
6825                project.worktrees().next().unwrap().read(cx).id()
6826            });
6827
6828            let handle = workspace
6829                .update(cx, |workspace, cx| {
6830                    let project_path = (worktree_id, "one.png");
6831                    workspace.open_path(project_path, None, true, cx)
6832                })
6833                .await
6834                .unwrap();
6835
6836            // This _must_ be the second item registered
6837            assert_eq!(
6838                handle.to_any().entity_type(),
6839                TypeId::of::<TestAlternatePngItemView>()
6840            );
6841
6842            let handle = workspace
6843                .update(cx, |workspace, cx| {
6844                    let project_path = (worktree_id, "three.txt");
6845                    workspace.open_path(project_path, None, true, cx)
6846                })
6847                .await;
6848            assert!(handle.is_err());
6849        }
6850    }
6851
6852    pub fn init_test(cx: &mut TestAppContext) {
6853        cx.update(|cx| {
6854            let settings_store = SettingsStore::test(cx);
6855            cx.set_global(settings_store);
6856            theme::init(theme::LoadThemes::JustBase, cx);
6857            language::init(cx);
6858            crate::init_settings(cx);
6859            Project::init_settings(cx);
6860        });
6861    }
6862}
6863
6864pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext) -> Stateful<Div> {
6865    const BORDER_SIZE: Pixels = px(1.0);
6866    let decorations = cx.window_decorations();
6867
6868    if matches!(decorations, Decorations::Client { .. }) {
6869        cx.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
6870    }
6871
6872    struct GlobalResizeEdge(ResizeEdge);
6873    impl Global for GlobalResizeEdge {}
6874
6875    div()
6876        .id("window-backdrop")
6877        .bg(transparent_black())
6878        .map(|div| match decorations {
6879            Decorations::Server => div,
6880            Decorations::Client { tiling, .. } => div
6881                .when(!(tiling.top || tiling.right), |div| {
6882                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6883                })
6884                .when(!(tiling.top || tiling.left), |div| {
6885                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6886                })
6887                .when(!(tiling.bottom || tiling.right), |div| {
6888                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6889                })
6890                .when(!(tiling.bottom || tiling.left), |div| {
6891                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6892                })
6893                .when(!tiling.top, |div| {
6894                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
6895                })
6896                .when(!tiling.bottom, |div| {
6897                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
6898                })
6899                .when(!tiling.left, |div| {
6900                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
6901                })
6902                .when(!tiling.right, |div| {
6903                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
6904                })
6905                .on_mouse_move(move |e, cx| {
6906                    let size = cx.window_bounds().get_bounds().size;
6907                    let pos = e.position;
6908
6909                    let new_edge =
6910                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
6911
6912                    let edge = cx.try_global::<GlobalResizeEdge>();
6913                    if new_edge != edge.map(|edge| edge.0) {
6914                        cx.window_handle()
6915                            .update(cx, |workspace, cx| cx.notify(workspace.entity_id()))
6916                            .ok();
6917                    }
6918                })
6919                .on_mouse_down(MouseButton::Left, move |e, cx| {
6920                    let size = cx.window_bounds().get_bounds().size;
6921                    let pos = e.position;
6922
6923                    let edge = match resize_edge(
6924                        pos,
6925                        theme::CLIENT_SIDE_DECORATION_SHADOW,
6926                        size,
6927                        tiling,
6928                    ) {
6929                        Some(value) => value,
6930                        None => return,
6931                    };
6932
6933                    cx.start_window_resize(edge);
6934                }),
6935        })
6936        .size_full()
6937        .child(
6938            div()
6939                .cursor(CursorStyle::Arrow)
6940                .map(|div| match decorations {
6941                    Decorations::Server => div,
6942                    Decorations::Client { tiling } => div
6943                        .border_color(cx.theme().colors().border)
6944                        .when(!(tiling.top || tiling.right), |div| {
6945                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6946                        })
6947                        .when(!(tiling.top || tiling.left), |div| {
6948                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6949                        })
6950                        .when(!(tiling.bottom || tiling.right), |div| {
6951                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6952                        })
6953                        .when(!(tiling.bottom || tiling.left), |div| {
6954                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6955                        })
6956                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
6957                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
6958                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
6959                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
6960                        .when(!tiling.is_tiled(), |div| {
6961                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
6962                                color: Hsla {
6963                                    h: 0.,
6964                                    s: 0.,
6965                                    l: 0.,
6966                                    a: 0.4,
6967                                },
6968                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
6969                                spread_radius: px(0.),
6970                                offset: point(px(0.0), px(0.0)),
6971                            }])
6972                        }),
6973                })
6974                .on_mouse_move(|_e, cx| {
6975                    cx.stop_propagation();
6976                })
6977                .size_full()
6978                .child(element),
6979        )
6980        .map(|div| match decorations {
6981            Decorations::Server => div,
6982            Decorations::Client { tiling, .. } => div.child(
6983                canvas(
6984                    |_bounds, cx| {
6985                        cx.insert_hitbox(
6986                            Bounds::new(
6987                                point(px(0.0), px(0.0)),
6988                                cx.window_bounds().get_bounds().size,
6989                            ),
6990                            false,
6991                        )
6992                    },
6993                    move |_bounds, hitbox, cx| {
6994                        let mouse = cx.mouse_position();
6995                        let size = cx.window_bounds().get_bounds().size;
6996                        let Some(edge) =
6997                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
6998                        else {
6999                            return;
7000                        };
7001                        cx.set_global(GlobalResizeEdge(edge));
7002                        cx.set_cursor_style(
7003                            match edge {
7004                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
7005                                ResizeEdge::Left | ResizeEdge::Right => {
7006                                    CursorStyle::ResizeLeftRight
7007                                }
7008                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
7009                                    CursorStyle::ResizeUpLeftDownRight
7010                                }
7011                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
7012                                    CursorStyle::ResizeUpRightDownLeft
7013                                }
7014                            },
7015                            &hitbox,
7016                        );
7017                    },
7018                )
7019                .size_full()
7020                .absolute(),
7021            ),
7022        })
7023}
7024
7025fn resize_edge(
7026    pos: Point<Pixels>,
7027    shadow_size: Pixels,
7028    window_size: Size<Pixels>,
7029    tiling: Tiling,
7030) -> Option<ResizeEdge> {
7031    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
7032    if bounds.contains(&pos) {
7033        return None;
7034    }
7035
7036    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
7037    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
7038    if !tiling.top && top_left_bounds.contains(&pos) {
7039        return Some(ResizeEdge::TopLeft);
7040    }
7041
7042    let top_right_bounds = Bounds::new(
7043        Point::new(window_size.width - corner_size.width, px(0.)),
7044        corner_size,
7045    );
7046    if !tiling.top && top_right_bounds.contains(&pos) {
7047        return Some(ResizeEdge::TopRight);
7048    }
7049
7050    let bottom_left_bounds = Bounds::new(
7051        Point::new(px(0.), window_size.height - corner_size.height),
7052        corner_size,
7053    );
7054    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
7055        return Some(ResizeEdge::BottomLeft);
7056    }
7057
7058    let bottom_right_bounds = Bounds::new(
7059        Point::new(
7060            window_size.width - corner_size.width,
7061            window_size.height - corner_size.height,
7062        ),
7063        corner_size,
7064    );
7065    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
7066        return Some(ResizeEdge::BottomRight);
7067    }
7068
7069    if !tiling.top && pos.y < shadow_size {
7070        Some(ResizeEdge::Top)
7071    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
7072        Some(ResizeEdge::Bottom)
7073    } else if !tiling.left && pos.x < shadow_size {
7074        Some(ResizeEdge::Left)
7075    } else if !tiling.right && pos.x > window_size.width - shadow_size {
7076        Some(ResizeEdge::Right)
7077    } else {
7078        None
7079    }
7080}