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