workspace.rs

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