workspace.rs

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