workspace.rs

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