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