workspace.rs

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