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, pinned_count) = {
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                    pane.pinned_count(),
4046                )
4047            };
4048
4049            SerializedPane::new(items, active, pinned_count)
4050        }
4051
4052        fn build_serialized_pane_group(
4053            pane_group: &Member,
4054            cx: &WindowContext,
4055        ) -> SerializedPaneGroup {
4056            match pane_group {
4057                Member::Axis(PaneAxis {
4058                    axis,
4059                    members,
4060                    flexes,
4061                    bounding_boxes: _,
4062                }) => SerializedPaneGroup::Group {
4063                    axis: SerializedAxis(*axis),
4064                    children: members
4065                        .iter()
4066                        .map(|member| build_serialized_pane_group(member, cx))
4067                        .collect::<Vec<_>>(),
4068                    flexes: Some(flexes.lock().clone()),
4069                },
4070                Member::Pane(pane_handle) => {
4071                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
4072                }
4073            }
4074        }
4075
4076        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
4077            let left_dock = this.left_dock.read(cx);
4078            let left_visible = left_dock.is_open();
4079            let left_active_panel = left_dock
4080                .visible_panel()
4081                .map(|panel| panel.persistent_name().to_string());
4082            let left_dock_zoom = left_dock
4083                .visible_panel()
4084                .map(|panel| panel.is_zoomed(cx))
4085                .unwrap_or(false);
4086
4087            let right_dock = this.right_dock.read(cx);
4088            let right_visible = right_dock.is_open();
4089            let right_active_panel = right_dock
4090                .visible_panel()
4091                .map(|panel| panel.persistent_name().to_string());
4092            let right_dock_zoom = right_dock
4093                .visible_panel()
4094                .map(|panel| panel.is_zoomed(cx))
4095                .unwrap_or(false);
4096
4097            let bottom_dock = this.bottom_dock.read(cx);
4098            let bottom_visible = bottom_dock.is_open();
4099            let bottom_active_panel = bottom_dock
4100                .visible_panel()
4101                .map(|panel| panel.persistent_name().to_string());
4102            let bottom_dock_zoom = bottom_dock
4103                .visible_panel()
4104                .map(|panel| panel.is_zoomed(cx))
4105                .unwrap_or(false);
4106
4107            DockStructure {
4108                left: DockData {
4109                    visible: left_visible,
4110                    active_panel: left_active_panel,
4111                    zoom: left_dock_zoom,
4112                },
4113                right: DockData {
4114                    visible: right_visible,
4115                    active_panel: right_active_panel,
4116                    zoom: right_dock_zoom,
4117                },
4118                bottom: DockData {
4119                    visible: bottom_visible,
4120                    active_panel: bottom_active_panel,
4121                    zoom: bottom_dock_zoom,
4122                },
4123            }
4124        }
4125
4126        let location = if let Some(local_paths) = self.local_paths(cx) {
4127            if !local_paths.is_empty() {
4128                Some(SerializedWorkspaceLocation::from_local_paths(local_paths))
4129            } else {
4130                None
4131            }
4132        } else if let Some(dev_server_project_id) = self.project().read(cx).dev_server_project_id()
4133        {
4134            let store = dev_server_projects::Store::global(cx).read(cx);
4135            maybe!({
4136                let project = store.dev_server_project(dev_server_project_id)?;
4137                let dev_server = store.dev_server(project.dev_server_id)?;
4138
4139                let dev_server_project = SerializedDevServerProject {
4140                    id: dev_server_project_id,
4141                    dev_server_name: dev_server.name.to_string(),
4142                    paths: project.paths.to_vec(),
4143                };
4144                Some(SerializedWorkspaceLocation::DevServer(dev_server_project))
4145            })
4146        } else {
4147            None
4148        };
4149
4150        if let Some(location) = location {
4151            let center_group = build_serialized_pane_group(&self.center.root, cx);
4152            let docks = build_serialized_docks(self, cx);
4153            let window_bounds = Some(SerializedWindowBounds(cx.window_bounds()));
4154            let serialized_workspace = SerializedWorkspace {
4155                id: database_id,
4156                location,
4157                center_group,
4158                window_bounds,
4159                display: Default::default(),
4160                docks,
4161                centered_layout: self.centered_layout,
4162                session_id: self.session_id.clone(),
4163                window_id: Some(cx.window_handle().window_id().as_u64()),
4164            };
4165            return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
4166        }
4167        Task::ready(())
4168    }
4169
4170    async fn serialize_items(
4171        this: &WeakView<Self>,
4172        items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
4173        cx: &mut AsyncWindowContext,
4174    ) -> Result<()> {
4175        const CHUNK_SIZE: usize = 200;
4176        const THROTTLE_TIME: Duration = Duration::from_millis(200);
4177
4178        let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
4179
4180        while let Some(items_received) = serializable_items.next().await {
4181            let unique_items =
4182                items_received
4183                    .into_iter()
4184                    .fold(HashMap::default(), |mut acc, item| {
4185                        acc.entry(item.item_id()).or_insert(item);
4186                        acc
4187                    });
4188
4189            // We use into_iter() here so that the references to the items are moved into
4190            // the tasks and not kept alive while we're sleeping.
4191            for (_, item) in unique_items.into_iter() {
4192                if let Ok(Some(task)) =
4193                    this.update(cx, |workspace, cx| item.serialize(workspace, false, cx))
4194                {
4195                    cx.background_executor()
4196                        .spawn(async move { task.await.log_err() })
4197                        .detach();
4198                }
4199            }
4200
4201            cx.background_executor().timer(THROTTLE_TIME).await;
4202        }
4203
4204        Ok(())
4205    }
4206
4207    pub(crate) fn enqueue_item_serialization(
4208        &mut self,
4209        item: Box<dyn SerializableItemHandle>,
4210    ) -> Result<()> {
4211        self.serializable_items_tx
4212            .unbounded_send(item)
4213            .map_err(|err| anyhow!("failed to send serializable item over channel: {}", err))
4214    }
4215
4216    pub(crate) fn load_workspace(
4217        serialized_workspace: SerializedWorkspace,
4218        paths_to_open: Vec<Option<ProjectPath>>,
4219        cx: &mut ViewContext<Workspace>,
4220    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
4221        cx.spawn(|workspace, mut cx| async move {
4222            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
4223
4224            let mut center_group = None;
4225            let mut center_items = None;
4226
4227            // Traverse the splits tree and add to things
4228            if let Some((group, active_pane, items)) = serialized_workspace
4229                .center_group
4230                .deserialize(
4231                    &project,
4232                    serialized_workspace.id,
4233                    workspace.clone(),
4234                    &mut cx,
4235                )
4236                .await
4237            {
4238                center_items = Some(items);
4239                center_group = Some((group, active_pane))
4240            }
4241
4242            let mut items_by_project_path = HashMap::default();
4243            let mut item_ids_by_kind = HashMap::default();
4244            let mut all_deserialized_items = Vec::default();
4245            cx.update(|cx| {
4246                for item in center_items.unwrap_or_default().into_iter().flatten() {
4247                    if let Some(serializable_item_handle) = item.to_serializable_item_handle(cx) {
4248                        item_ids_by_kind
4249                            .entry(serializable_item_handle.serialized_item_kind())
4250                            .or_insert(Vec::new())
4251                            .push(item.item_id().as_u64() as ItemId);
4252                    }
4253
4254                    if let Some(project_path) = item.project_path(cx) {
4255                        items_by_project_path.insert(project_path, item.clone());
4256                    }
4257                    all_deserialized_items.push(item);
4258                }
4259            })?;
4260
4261            let opened_items = paths_to_open
4262                .into_iter()
4263                .map(|path_to_open| {
4264                    path_to_open
4265                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
4266                })
4267                .collect::<Vec<_>>();
4268
4269            // Remove old panes from workspace panes list
4270            workspace.update(&mut cx, |workspace, cx| {
4271                if let Some((center_group, active_pane)) = center_group {
4272                    workspace.remove_panes(workspace.center.root.clone(), cx);
4273
4274                    // Swap workspace center group
4275                    workspace.center = PaneGroup::with_root(center_group);
4276                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
4277                    if let Some(active_pane) = active_pane {
4278                        workspace.active_pane = active_pane;
4279                        cx.focus_self();
4280                    } else {
4281                        workspace.active_pane = workspace.center.first_pane().clone();
4282                    }
4283                }
4284
4285                let docks = serialized_workspace.docks;
4286
4287                for (dock, serialized_dock) in [
4288                    (&mut workspace.right_dock, docks.right),
4289                    (&mut workspace.left_dock, docks.left),
4290                    (&mut workspace.bottom_dock, docks.bottom),
4291                ]
4292                .iter_mut()
4293                {
4294                    dock.update(cx, |dock, cx| {
4295                        dock.serialized_dock = Some(serialized_dock.clone());
4296                        dock.restore_state(cx);
4297                    });
4298                }
4299
4300                cx.notify();
4301            })?;
4302
4303            // Clean up all the items that have _not_ been loaded. Our ItemIds aren't stable. That means
4304            // after loading the items, we might have different items and in order to avoid
4305            // the database filling up, we delete items that haven't been loaded now.
4306            //
4307            // The items that have been loaded, have been saved after they've been added to the workspace.
4308            let clean_up_tasks = workspace.update(&mut cx, |_, cx| {
4309                item_ids_by_kind
4310                    .into_iter()
4311                    .map(|(item_kind, loaded_items)| {
4312                        SerializableItemRegistry::cleanup(
4313                            item_kind,
4314                            serialized_workspace.id,
4315                            loaded_items,
4316                            cx,
4317                        )
4318                        .log_err()
4319                    })
4320                    .collect::<Vec<_>>()
4321            })?;
4322
4323            futures::future::join_all(clean_up_tasks).await;
4324
4325            workspace
4326                .update(&mut cx, |workspace, cx| {
4327                    // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
4328                    workspace.serialize_workspace_internal(cx).detach();
4329
4330                    // Ensure that we mark the window as edited if we did load dirty items
4331                    workspace.update_window_edited(cx);
4332                })
4333                .ok();
4334
4335            Ok(opened_items)
4336        })
4337    }
4338
4339    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
4340        self.add_workspace_actions_listeners(div, cx)
4341            .on_action(cx.listener(Self::close_inactive_items_and_panes))
4342            .on_action(cx.listener(Self::close_all_items_and_panes))
4343            .on_action(cx.listener(Self::save_all))
4344            .on_action(cx.listener(Self::send_keystrokes))
4345            .on_action(cx.listener(Self::add_folder_to_project))
4346            .on_action(cx.listener(Self::follow_next_collaborator))
4347            .on_action(cx.listener(Self::open))
4348            .on_action(cx.listener(Self::close_window))
4349            .on_action(cx.listener(Self::activate_pane_at_index))
4350            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
4351                let pane = workspace.active_pane().clone();
4352                workspace.unfollow_in_pane(&pane, cx);
4353            }))
4354            .on_action(cx.listener(|workspace, action: &Save, cx| {
4355                workspace
4356                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
4357                    .detach_and_log_err(cx);
4358            }))
4359            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
4360                workspace
4361                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
4362                    .detach_and_log_err(cx);
4363            }))
4364            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
4365                workspace
4366                    .save_active_item(SaveIntent::SaveAs, cx)
4367                    .detach_and_log_err(cx);
4368            }))
4369            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
4370                workspace.activate_previous_pane(cx)
4371            }))
4372            .on_action(
4373                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
4374            )
4375            .on_action(
4376                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
4377                    workspace.activate_pane_in_direction(action.0, cx)
4378                }),
4379            )
4380            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
4381                workspace.swap_pane_in_direction(action.0, cx)
4382            }))
4383            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
4384                this.toggle_dock(DockPosition::Left, cx);
4385            }))
4386            .on_action(
4387                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
4388                    workspace.toggle_dock(DockPosition::Right, cx);
4389                }),
4390            )
4391            .on_action(
4392                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
4393                    workspace.toggle_dock(DockPosition::Bottom, cx);
4394                }),
4395            )
4396            .on_action(
4397                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
4398                    workspace.close_all_docks(cx);
4399                }),
4400            )
4401            .on_action(
4402                cx.listener(|workspace: &mut Workspace, _: &ClearAllNotifications, cx| {
4403                    workspace.clear_all_notifications(cx);
4404                }),
4405            )
4406            .on_action(
4407                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
4408                    workspace.reopen_closed_item(cx).detach();
4409                }),
4410            )
4411            .on_action(cx.listener(Workspace::toggle_centered_layout))
4412    }
4413
4414    #[cfg(any(test, feature = "test-support"))]
4415    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
4416        use node_runtime::FakeNodeRuntime;
4417        use session::Session;
4418
4419        let client = project.read(cx).client();
4420        let user_store = project.read(cx).user_store();
4421
4422        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
4423        let session = cx.new_model(|cx| AppSession::new(Session::test(), cx));
4424        cx.activate_window();
4425        let app_state = Arc::new(AppState {
4426            languages: project.read(cx).languages().clone(),
4427            workspace_store,
4428            client,
4429            user_store,
4430            fs: project.read(cx).fs().clone(),
4431            build_window_options: |_, _| Default::default(),
4432            node_runtime: FakeNodeRuntime::new(),
4433            session,
4434        });
4435        let workspace = Self::new(Default::default(), project, app_state, cx);
4436        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
4437        workspace
4438    }
4439
4440    pub fn register_action<A: Action>(
4441        &mut self,
4442        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
4443    ) -> &mut Self {
4444        let callback = Arc::new(callback);
4445
4446        self.workspace_actions.push(Box::new(move |div, cx| {
4447            let callback = callback.clone();
4448            div.on_action(
4449                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
4450            )
4451        }));
4452        self
4453    }
4454
4455    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
4456        for action in self.workspace_actions.iter() {
4457            div = (action)(div, cx)
4458        }
4459        div
4460    }
4461
4462    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
4463        self.modal_layer.read(cx).has_active_modal()
4464    }
4465
4466    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
4467        self.modal_layer.read(cx).active_modal()
4468    }
4469
4470    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
4471    where
4472        B: FnOnce(&mut ViewContext<V>) -> V,
4473    {
4474        self.modal_layer
4475            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
4476    }
4477
4478    pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
4479        self.centered_layout = !self.centered_layout;
4480        if let Some(database_id) = self.database_id() {
4481            cx.background_executor()
4482                .spawn(DB.set_centered_layout(database_id, self.centered_layout))
4483                .detach_and_log_err(cx);
4484        }
4485        cx.notify();
4486    }
4487
4488    fn adjust_padding(padding: Option<f32>) -> f32 {
4489        padding
4490            .unwrap_or(Self::DEFAULT_PADDING)
4491            .clamp(0.0, Self::MAX_PADDING)
4492    }
4493
4494    fn render_dock(
4495        &self,
4496        position: DockPosition,
4497        dock: &View<Dock>,
4498        cx: &WindowContext,
4499    ) -> Option<Div> {
4500        if self.zoomed_position == Some(position) {
4501            return None;
4502        }
4503
4504        let leader_border = dock.read(cx).active_panel().and_then(|panel| {
4505            let pane = panel.pane(cx)?;
4506            let follower_states = &self.follower_states;
4507            leader_border_for_pane(follower_states, &pane, cx)
4508        });
4509
4510        Some(
4511            div()
4512                .flex()
4513                .flex_none()
4514                .overflow_hidden()
4515                .child(dock.clone())
4516                .children(leader_border),
4517        )
4518    }
4519}
4520
4521fn leader_border_for_pane(
4522    follower_states: &HashMap<PeerId, FollowerState>,
4523    pane: &View<Pane>,
4524    cx: &WindowContext,
4525) -> Option<Div> {
4526    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
4527        if state.pane() == pane {
4528            Some((*leader_id, state))
4529        } else {
4530            None
4531        }
4532    })?;
4533
4534    let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
4535    let leader = room.remote_participant_for_peer_id(leader_id)?;
4536
4537    let mut leader_color = cx
4538        .theme()
4539        .players()
4540        .color_for_participant(leader.participant_index.0)
4541        .cursor;
4542    leader_color.fade_out(0.3);
4543    Some(
4544        div()
4545            .absolute()
4546            .size_full()
4547            .left_0()
4548            .top_0()
4549            .border_2()
4550            .border_color(leader_color),
4551    )
4552}
4553
4554fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
4555    ZED_WINDOW_POSITION
4556        .zip(*ZED_WINDOW_SIZE)
4557        .map(|(position, size)| Bounds {
4558            origin: position,
4559            size,
4560        })
4561}
4562
4563fn open_items(
4564    serialized_workspace: Option<SerializedWorkspace>,
4565    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
4566    app_state: Arc<AppState>,
4567    cx: &mut ViewContext<Workspace>,
4568) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
4569    let restored_items = serialized_workspace.map(|serialized_workspace| {
4570        Workspace::load_workspace(
4571            serialized_workspace,
4572            project_paths_to_open
4573                .iter()
4574                .map(|(_, project_path)| project_path)
4575                .cloned()
4576                .collect(),
4577            cx,
4578        )
4579    });
4580
4581    cx.spawn(|workspace, mut cx| async move {
4582        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
4583
4584        if let Some(restored_items) = restored_items {
4585            let restored_items = restored_items.await?;
4586
4587            let restored_project_paths = restored_items
4588                .iter()
4589                .filter_map(|item| {
4590                    cx.update(|cx| item.as_ref()?.project_path(cx))
4591                        .ok()
4592                        .flatten()
4593                })
4594                .collect::<HashSet<_>>();
4595
4596            for restored_item in restored_items {
4597                opened_items.push(restored_item.map(Ok));
4598            }
4599
4600            project_paths_to_open
4601                .iter_mut()
4602                .for_each(|(_, project_path)| {
4603                    if let Some(project_path_to_open) = project_path {
4604                        if restored_project_paths.contains(project_path_to_open) {
4605                            *project_path = None;
4606                        }
4607                    }
4608                });
4609        } else {
4610            for _ in 0..project_paths_to_open.len() {
4611                opened_items.push(None);
4612            }
4613        }
4614        assert!(opened_items.len() == project_paths_to_open.len());
4615
4616        let tasks =
4617            project_paths_to_open
4618                .into_iter()
4619                .enumerate()
4620                .map(|(ix, (abs_path, project_path))| {
4621                    let workspace = workspace.clone();
4622                    cx.spawn(|mut cx| {
4623                        let fs = app_state.fs.clone();
4624                        async move {
4625                            let file_project_path = project_path?;
4626                            if fs.is_dir(&abs_path).await {
4627                                None
4628                            } else {
4629                                Some((
4630                                    ix,
4631                                    workspace
4632                                        .update(&mut cx, |workspace, cx| {
4633                                            workspace.open_path(file_project_path, None, true, cx)
4634                                        })
4635                                        .log_err()?
4636                                        .await,
4637                                ))
4638                            }
4639                        }
4640                    })
4641                });
4642
4643        let tasks = tasks.collect::<Vec<_>>();
4644
4645        let tasks = futures::future::join_all(tasks);
4646        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
4647            opened_items[ix] = Some(path_open_result);
4648        }
4649
4650        Ok(opened_items)
4651    })
4652}
4653
4654enum ActivateInDirectionTarget {
4655    Pane(View<Pane>),
4656    Dock(View<Dock>),
4657}
4658
4659fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
4660    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
4661
4662    workspace
4663        .update(cx, |workspace, cx| {
4664            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
4665                struct DatabaseFailedNotification;
4666
4667                workspace.show_notification_once(
4668                    NotificationId::unique::<DatabaseFailedNotification>(),
4669                    cx,
4670                    |cx| {
4671                        cx.new_view(|_| {
4672                            MessageNotification::new("Failed to load the database file.")
4673                                .with_click_message("Click to let us know about this error")
4674                                .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
4675                        })
4676                    },
4677                );
4678            }
4679        })
4680        .log_err();
4681}
4682
4683impl FocusableView for Workspace {
4684    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4685        self.active_pane.focus_handle(cx)
4686    }
4687}
4688
4689#[derive(Clone, Render)]
4690struct DraggedDock(DockPosition);
4691
4692impl Render for Workspace {
4693    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4694        let mut context = KeyContext::new_with_defaults();
4695        context.add("Workspace");
4696        let centered_layout = self.centered_layout
4697            && self.center.panes().len() == 1
4698            && self.active_item(cx).is_some();
4699        let render_padding = |size| {
4700            (size > 0.0).then(|| {
4701                div()
4702                    .h_full()
4703                    .w(relative(size))
4704                    .bg(cx.theme().colors().editor_background)
4705                    .border_color(cx.theme().colors().pane_group_border)
4706            })
4707        };
4708        let paddings = if centered_layout {
4709            let settings = WorkspaceSettings::get_global(cx).centered_layout;
4710            (
4711                render_padding(Self::adjust_padding(settings.left_padding)),
4712                render_padding(Self::adjust_padding(settings.right_padding)),
4713            )
4714        } else {
4715            (None, None)
4716        };
4717        let ui_font = theme::setup_ui_font(cx);
4718
4719        let theme = cx.theme().clone();
4720        let colors = theme.colors();
4721
4722        client_side_decorations(
4723            self.actions(div(), cx)
4724                .key_context(context)
4725                .relative()
4726                .size_full()
4727                .flex()
4728                .flex_col()
4729                .font(ui_font)
4730                .gap_0()
4731                .justify_start()
4732                .items_start()
4733                .text_color(colors.text)
4734                .overflow_hidden()
4735                .children(self.titlebar_item.clone())
4736                .child(
4737                    div()
4738                        .id("workspace")
4739                        .bg(colors.background)
4740                        .relative()
4741                        .flex_1()
4742                        .w_full()
4743                        .flex()
4744                        .flex_col()
4745                        .overflow_hidden()
4746                        .border_t_1()
4747                        .border_b_1()
4748                        .border_color(colors.border)
4749                        .child({
4750                            let this = cx.view().clone();
4751                            canvas(
4752                                move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
4753                                |_, _, _| {},
4754                            )
4755                            .absolute()
4756                            .size_full()
4757                        })
4758                        .when(self.zoomed.is_none(), |this| {
4759                            this.on_drag_move(cx.listener(
4760                                |workspace, e: &DragMoveEvent<DraggedDock>, cx| match e.drag(cx).0 {
4761                                    DockPosition::Left => {
4762                                        let size = e.event.position.x - workspace.bounds.left();
4763                                        workspace.left_dock.update(cx, |left_dock, cx| {
4764                                            left_dock.resize_active_panel(Some(size), cx);
4765                                        });
4766                                    }
4767                                    DockPosition::Right => {
4768                                        let size = workspace.bounds.right() - e.event.position.x;
4769                                        workspace.right_dock.update(cx, |right_dock, cx| {
4770                                            right_dock.resize_active_panel(Some(size), cx);
4771                                        });
4772                                    }
4773                                    DockPosition::Bottom => {
4774                                        let size = workspace.bounds.bottom() - e.event.position.y;
4775                                        workspace.bottom_dock.update(cx, |bottom_dock, cx| {
4776                                            bottom_dock.resize_active_panel(Some(size), cx);
4777                                        });
4778                                    }
4779                                },
4780                            ))
4781                        })
4782                        .child(
4783                            div()
4784                                .flex()
4785                                .flex_row()
4786                                .h_full()
4787                                // Left Dock
4788                                .children(self.render_dock(DockPosition::Left, &self.left_dock, cx))
4789                                // Panes
4790                                .child(
4791                                    div()
4792                                        .flex()
4793                                        .flex_col()
4794                                        .flex_1()
4795                                        .overflow_hidden()
4796                                        .child(
4797                                            h_flex()
4798                                                .flex_1()
4799                                                .when_some(paddings.0, |this, p| {
4800                                                    this.child(p.border_r_1())
4801                                                })
4802                                                .child(self.center.render(
4803                                                    &self.project,
4804                                                    &self.follower_states,
4805                                                    self.active_call(),
4806                                                    &self.active_pane,
4807                                                    self.zoomed.as_ref(),
4808                                                    &self.app_state,
4809                                                    cx,
4810                                                ))
4811                                                .when_some(paddings.1, |this, p| {
4812                                                    this.child(p.border_l_1())
4813                                                }),
4814                                        )
4815                                        .children(self.render_dock(
4816                                            DockPosition::Bottom,
4817                                            &self.bottom_dock,
4818                                            cx,
4819                                        )),
4820                                )
4821                                // Right Dock
4822                                .children(self.render_dock(
4823                                    DockPosition::Right,
4824                                    &self.right_dock,
4825                                    cx,
4826                                )),
4827                        )
4828                        .children(self.zoomed.as_ref().and_then(|view| {
4829                            let zoomed_view = view.upgrade()?;
4830                            let div = div()
4831                                .occlude()
4832                                .absolute()
4833                                .overflow_hidden()
4834                                .border_color(colors.border)
4835                                .bg(colors.background)
4836                                .child(zoomed_view)
4837                                .inset_0()
4838                                .shadow_lg();
4839
4840                            Some(match self.zoomed_position {
4841                                Some(DockPosition::Left) => div.right_2().border_r_1(),
4842                                Some(DockPosition::Right) => div.left_2().border_l_1(),
4843                                Some(DockPosition::Bottom) => div.top_2().border_t_1(),
4844                                None => div.top_2().bottom_2().left_2().right_2().border_1(),
4845                            })
4846                        }))
4847                        .child(self.modal_layer.clone())
4848                        .children(self.render_notifications(cx)),
4849                )
4850                .child(self.status_bar.clone())
4851                .children(if self.project.read(cx).is_disconnected() {
4852                    if let Some(render) = self.render_disconnected_overlay.take() {
4853                        let result = render(self, cx);
4854                        self.render_disconnected_overlay = Some(render);
4855                        Some(result)
4856                    } else {
4857                        None
4858                    }
4859                } else {
4860                    None
4861                }),
4862            cx,
4863        )
4864    }
4865}
4866
4867impl WorkspaceStore {
4868    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4869        Self {
4870            workspaces: Default::default(),
4871            _subscriptions: vec![
4872                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4873                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4874            ],
4875            client,
4876        }
4877    }
4878
4879    pub fn update_followers(
4880        &self,
4881        project_id: Option<u64>,
4882        update: proto::update_followers::Variant,
4883        cx: &AppContext,
4884    ) -> Option<()> {
4885        let active_call = ActiveCall::try_global(cx)?;
4886        let room_id = active_call.read(cx).room()?.read(cx).id();
4887        self.client
4888            .send(proto::UpdateFollowers {
4889                room_id,
4890                project_id,
4891                variant: Some(update),
4892            })
4893            .log_err()
4894    }
4895
4896    pub async fn handle_follow(
4897        this: Model<Self>,
4898        envelope: TypedEnvelope<proto::Follow>,
4899        mut cx: AsyncAppContext,
4900    ) -> Result<proto::FollowResponse> {
4901        this.update(&mut cx, |this, cx| {
4902            let follower = Follower {
4903                project_id: envelope.payload.project_id,
4904                peer_id: envelope.original_sender_id()?,
4905            };
4906
4907            let mut response = proto::FollowResponse::default();
4908            this.workspaces.retain(|workspace| {
4909                workspace
4910                    .update(cx, |workspace, cx| {
4911                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4912                        if let Some(active_view) = handler_response.active_view.clone() {
4913                            if workspace.project.read(cx).remote_id() == follower.project_id {
4914                                response.active_view = Some(active_view)
4915                            }
4916                        }
4917                    })
4918                    .is_ok()
4919            });
4920
4921            Ok(response)
4922        })?
4923    }
4924
4925    async fn handle_update_followers(
4926        this: Model<Self>,
4927        envelope: TypedEnvelope<proto::UpdateFollowers>,
4928        mut cx: AsyncAppContext,
4929    ) -> Result<()> {
4930        let leader_id = envelope.original_sender_id()?;
4931        let update = envelope.payload;
4932
4933        this.update(&mut cx, |this, cx| {
4934            this.workspaces.retain(|workspace| {
4935                workspace
4936                    .update(cx, |workspace, cx| {
4937                        let project_id = workspace.project.read(cx).remote_id();
4938                        if update.project_id != project_id && update.project_id.is_some() {
4939                            return;
4940                        }
4941                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4942                    })
4943                    .is_ok()
4944            });
4945            Ok(())
4946        })?
4947    }
4948}
4949
4950impl ViewId {
4951    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4952        Ok(Self {
4953            creator: message
4954                .creator
4955                .ok_or_else(|| anyhow!("creator is missing"))?,
4956            id: message.id,
4957        })
4958    }
4959
4960    pub(crate) fn to_proto(self) -> proto::ViewId {
4961        proto::ViewId {
4962            creator: Some(self.creator),
4963            id: self.id,
4964        }
4965    }
4966}
4967
4968impl FollowerState {
4969    fn pane(&self) -> &View<Pane> {
4970        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
4971    }
4972}
4973
4974pub trait WorkspaceHandle {
4975    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4976}
4977
4978impl WorkspaceHandle for View<Workspace> {
4979    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4980        self.read(cx)
4981            .worktrees(cx)
4982            .flat_map(|worktree| {
4983                let worktree_id = worktree.read(cx).id();
4984                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4985                    worktree_id,
4986                    path: f.path.clone(),
4987                })
4988            })
4989            .collect::<Vec<_>>()
4990    }
4991}
4992
4993impl std::fmt::Debug for OpenPaths {
4994    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4995        f.debug_struct("OpenPaths")
4996            .field("paths", &self.paths)
4997            .finish()
4998    }
4999}
5000
5001pub fn activate_workspace_for_project(
5002    cx: &mut AppContext,
5003    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
5004) -> Option<WindowHandle<Workspace>> {
5005    for window in cx.windows() {
5006        let Some(workspace) = window.downcast::<Workspace>() else {
5007            continue;
5008        };
5009
5010        let predicate = workspace
5011            .update(cx, |workspace, cx| {
5012                let project = workspace.project.read(cx);
5013                if predicate(project, cx) {
5014                    cx.activate_window();
5015                    true
5016                } else {
5017                    false
5018                }
5019            })
5020            .log_err()
5021            .unwrap_or(false);
5022
5023        if predicate {
5024            return Some(workspace);
5025        }
5026    }
5027
5028    None
5029}
5030
5031pub async fn last_opened_workspace_paths() -> Option<LocalPaths> {
5032    DB.last_workspace().await.log_err().flatten()
5033}
5034
5035pub fn last_session_workspace_locations(
5036    last_session_id: &str,
5037    last_session_window_stack: Option<Vec<WindowId>>,
5038) -> Option<Vec<LocalPaths>> {
5039    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
5040        .log_err()
5041}
5042
5043actions!(collab, [OpenChannelNotes]);
5044actions!(zed, [OpenLog]);
5045
5046async fn join_channel_internal(
5047    channel_id: ChannelId,
5048    app_state: &Arc<AppState>,
5049    requesting_window: Option<WindowHandle<Workspace>>,
5050    active_call: &Model<ActiveCall>,
5051    cx: &mut AsyncAppContext,
5052) -> Result<bool> {
5053    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
5054        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
5055            return (false, None);
5056        };
5057
5058        let already_in_channel = room.channel_id() == Some(channel_id);
5059        let should_prompt = room.is_sharing_project()
5060            && !room.remote_participants().is_empty()
5061            && !already_in_channel;
5062        let open_room = if already_in_channel {
5063            active_call.room().cloned()
5064        } else {
5065            None
5066        };
5067        (should_prompt, open_room)
5068    })?;
5069
5070    if let Some(room) = open_room {
5071        let task = room.update(cx, |room, cx| {
5072            if let Some((project, host)) = room.most_active_project(cx) {
5073                return Some(join_in_room_project(project, host, app_state.clone(), cx));
5074            }
5075
5076            None
5077        })?;
5078        if let Some(task) = task {
5079            task.await?;
5080        }
5081        return anyhow::Ok(true);
5082    }
5083
5084    if should_prompt {
5085        if let Some(workspace) = requesting_window {
5086            let answer = workspace
5087                .update(cx, |_, cx| {
5088                    cx.prompt(
5089                        PromptLevel::Warning,
5090                        "Do you want to switch channels?",
5091                        Some("Leaving this call will unshare your current project."),
5092                        &["Yes, Join Channel", "Cancel"],
5093                    )
5094                })?
5095                .await;
5096
5097            if answer == Ok(1) {
5098                return Ok(false);
5099            }
5100        } else {
5101            return Ok(false); // unreachable!() hopefully
5102        }
5103    }
5104
5105    let client = cx.update(|cx| active_call.read(cx).client())?;
5106
5107    let mut client_status = client.status();
5108
5109    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
5110    'outer: loop {
5111        let Some(status) = client_status.recv().await else {
5112            return Err(anyhow!("error connecting"));
5113        };
5114
5115        match status {
5116            Status::Connecting
5117            | Status::Authenticating
5118            | Status::Reconnecting
5119            | Status::Reauthenticating => continue,
5120            Status::Connected { .. } => break 'outer,
5121            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
5122            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
5123            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
5124                return Err(ErrorCode::Disconnected.into());
5125            }
5126        }
5127    }
5128
5129    let room = active_call
5130        .update(cx, |active_call, cx| {
5131            active_call.join_channel(channel_id, cx)
5132        })?
5133        .await?;
5134
5135    let Some(room) = room else {
5136        return anyhow::Ok(true);
5137    };
5138
5139    room.update(cx, |room, _| room.room_update_completed())?
5140        .await;
5141
5142    let task = room.update(cx, |room, cx| {
5143        if let Some((project, host)) = room.most_active_project(cx) {
5144            return Some(join_in_room_project(project, host, app_state.clone(), cx));
5145        }
5146
5147        // If you are the first to join a channel, see if you should share your project.
5148        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
5149            if let Some(workspace) = requesting_window {
5150                let project = workspace.update(cx, |workspace, cx| {
5151                    let project = workspace.project.read(cx);
5152                    let is_dev_server = project.dev_server_project_id().is_some();
5153
5154                    if !is_dev_server && !CallSettings::get_global(cx).share_on_join {
5155                        return None;
5156                    }
5157
5158                    if (project.is_local_or_ssh() || is_dev_server)
5159                        && project.visible_worktrees(cx).any(|tree| {
5160                            tree.read(cx)
5161                                .root_entry()
5162                                .map_or(false, |entry| entry.is_dir())
5163                        })
5164                    {
5165                        Some(workspace.project.clone())
5166                    } else {
5167                        None
5168                    }
5169                });
5170                if let Ok(Some(project)) = project {
5171                    return Some(cx.spawn(|room, mut cx| async move {
5172                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
5173                            .await?;
5174                        Ok(())
5175                    }));
5176                }
5177            }
5178        }
5179
5180        None
5181    })?;
5182    if let Some(task) = task {
5183        task.await?;
5184        return anyhow::Ok(true);
5185    }
5186    anyhow::Ok(false)
5187}
5188
5189pub fn join_channel(
5190    channel_id: ChannelId,
5191    app_state: Arc<AppState>,
5192    requesting_window: Option<WindowHandle<Workspace>>,
5193    cx: &mut AppContext,
5194) -> Task<Result<()>> {
5195    let active_call = ActiveCall::global(cx);
5196    cx.spawn(|mut cx| async move {
5197        let result = join_channel_internal(
5198            channel_id,
5199            &app_state,
5200            requesting_window,
5201            &active_call,
5202            &mut cx,
5203        )
5204            .await;
5205
5206        // join channel succeeded, and opened a window
5207        if matches!(result, Ok(true)) {
5208            return anyhow::Ok(());
5209        }
5210
5211        // find an existing workspace to focus and show call controls
5212        let mut active_window =
5213            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
5214        if active_window.is_none() {
5215            // no open workspaces, make one to show the error in (blergh)
5216            let (window_handle, _) = cx
5217                .update(|cx| {
5218                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
5219                })?
5220                .await?;
5221
5222            if result.is_ok() {
5223                cx.update(|cx| {
5224                    cx.dispatch_action(&OpenChannelNotes);
5225                }).log_err();
5226            }
5227
5228            active_window = Some(window_handle);
5229        }
5230
5231        if let Err(err) = result {
5232            log::error!("failed to join channel: {}", err);
5233            if let Some(active_window) = active_window {
5234                active_window
5235                    .update(&mut cx, |_, cx| {
5236                        let detail: SharedString = match err.error_code() {
5237                            ErrorCode::SignedOut => {
5238                                "Please sign in to continue.".into()
5239                            }
5240                            ErrorCode::UpgradeRequired => {
5241                                "Your are running an unsupported version of Zed. Please update to continue.".into()
5242                            }
5243                            ErrorCode::NoSuchChannel => {
5244                                "No matching channel was found. Please check the link and try again.".into()
5245                            }
5246                            ErrorCode::Forbidden => {
5247                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
5248                            }
5249                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
5250                            _ => format!("{}\n\nPlease try again.", err).into(),
5251                        };
5252                        cx.prompt(
5253                            PromptLevel::Critical,
5254                            "Failed to join channel",
5255                            Some(&detail),
5256                            &["Ok"],
5257                        )
5258                    })?
5259                    .await
5260                    .ok();
5261            }
5262        }
5263
5264        // return ok, we showed the error to the user.
5265        anyhow::Ok(())
5266    })
5267}
5268
5269pub async fn get_any_active_workspace(
5270    app_state: Arc<AppState>,
5271    mut cx: AsyncAppContext,
5272) -> anyhow::Result<WindowHandle<Workspace>> {
5273    // find an existing workspace to focus and show call controls
5274    let active_window = activate_any_workspace_window(&mut cx);
5275    if active_window.is_none() {
5276        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
5277            .await?;
5278    }
5279    activate_any_workspace_window(&mut cx).context("could not open zed")
5280}
5281
5282fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
5283    cx.update(|cx| {
5284        if let Some(workspace_window) = cx
5285            .active_window()
5286            .and_then(|window| window.downcast::<Workspace>())
5287        {
5288            return Some(workspace_window);
5289        }
5290
5291        for window in cx.windows() {
5292            if let Some(workspace_window) = window.downcast::<Workspace>() {
5293                workspace_window
5294                    .update(cx, |_, cx| cx.activate_window())
5295                    .ok();
5296                return Some(workspace_window);
5297            }
5298        }
5299        None
5300    })
5301    .ok()
5302    .flatten()
5303}
5304
5305pub fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
5306    cx.windows()
5307        .into_iter()
5308        .filter_map(|window| window.downcast::<Workspace>())
5309        .filter(|workspace| {
5310            workspace
5311                .read(cx)
5312                .is_ok_and(|workspace| workspace.project.read(cx).is_local_or_ssh())
5313        })
5314        .collect()
5315}
5316
5317#[derive(Default)]
5318pub struct OpenOptions {
5319    pub open_new_workspace: Option<bool>,
5320    pub replace_window: Option<WindowHandle<Workspace>>,
5321    pub env: Option<HashMap<String, String>>,
5322}
5323
5324#[allow(clippy::type_complexity)]
5325pub fn open_paths(
5326    abs_paths: &[PathBuf],
5327    app_state: Arc<AppState>,
5328    open_options: OpenOptions,
5329    cx: &mut AppContext,
5330) -> Task<
5331    anyhow::Result<(
5332        WindowHandle<Workspace>,
5333        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
5334    )>,
5335> {
5336    let abs_paths = abs_paths.to_vec();
5337    let mut existing = None;
5338    let mut best_match = None;
5339    let mut open_visible = OpenVisible::All;
5340
5341    if open_options.open_new_workspace != Some(true) {
5342        for window in local_workspace_windows(cx) {
5343            if let Ok(workspace) = window.read(cx) {
5344                let m = workspace
5345                    .project
5346                    .read(cx)
5347                    .visibility_for_paths(&abs_paths, cx);
5348                if m > best_match {
5349                    existing = Some(window);
5350                    best_match = m;
5351                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
5352                    existing = Some(window)
5353                }
5354            }
5355        }
5356    }
5357
5358    cx.spawn(move |mut cx| async move {
5359        if open_options.open_new_workspace.is_none() && existing.is_none() {
5360            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
5361            if futures::future::join_all(all_files)
5362                .await
5363                .into_iter()
5364                .filter_map(|result| result.ok().flatten())
5365                .all(|file| !file.is_dir)
5366            {
5367                cx.update(|cx| {
5368                    for window in local_workspace_windows(cx) {
5369                        if let Ok(workspace) = window.read(cx) {
5370                            let project = workspace.project().read(cx);
5371                            if project.is_via_collab() {
5372                                continue;
5373                            }
5374                            existing = Some(window);
5375                            open_visible = OpenVisible::None;
5376                            break;
5377                        }
5378                    }
5379                })?;
5380            }
5381        }
5382
5383        if let Some(existing) = existing {
5384            Ok((
5385                existing,
5386                existing
5387                    .update(&mut cx, |workspace, cx| {
5388                        cx.activate_window();
5389                        workspace.open_paths(abs_paths, open_visible, None, cx)
5390                    })?
5391                    .await,
5392            ))
5393        } else {
5394            cx.update(move |cx| {
5395                Workspace::new_local(
5396                    abs_paths,
5397                    app_state.clone(),
5398                    open_options.replace_window,
5399                    open_options.env,
5400                    cx,
5401                )
5402            })?
5403            .await
5404        }
5405    })
5406}
5407
5408pub fn open_new(
5409    open_options: OpenOptions,
5410    app_state: Arc<AppState>,
5411    cx: &mut AppContext,
5412    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
5413) -> Task<anyhow::Result<()>> {
5414    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
5415    cx.spawn(|mut cx| async move {
5416        let (workspace, opened_paths) = task.await?;
5417        workspace.update(&mut cx, |workspace, cx| {
5418            if opened_paths.is_empty() {
5419                init(workspace, cx)
5420            }
5421        })?;
5422        Ok(())
5423    })
5424}
5425
5426pub fn create_and_open_local_file(
5427    path: &'static Path,
5428    cx: &mut ViewContext<Workspace>,
5429    default_content: impl 'static + Send + FnOnce() -> Rope,
5430) -> Task<Result<Box<dyn ItemHandle>>> {
5431    cx.spawn(|workspace, mut cx| async move {
5432        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
5433        if !fs.is_file(path).await {
5434            fs.create_file(path, Default::default()).await?;
5435            fs.save(path, &default_content(), Default::default())
5436                .await?;
5437        }
5438
5439        let mut items = workspace
5440            .update(&mut cx, |workspace, cx| {
5441                workspace.with_local_workspace(cx, |workspace, cx| {
5442                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
5443                })
5444            })?
5445            .await?
5446            .await;
5447
5448        let item = items.pop().flatten();
5449        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
5450    })
5451}
5452
5453pub fn join_hosted_project(
5454    hosted_project_id: ProjectId,
5455    app_state: Arc<AppState>,
5456    cx: &mut AppContext,
5457) -> Task<Result<()>> {
5458    cx.spawn(|mut cx| async move {
5459        let existing_window = cx.update(|cx| {
5460            cx.windows().into_iter().find_map(|window| {
5461                let workspace = window.downcast::<Workspace>()?;
5462                workspace
5463                    .read(cx)
5464                    .is_ok_and(|workspace| {
5465                        workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
5466                    })
5467                    .then_some(workspace)
5468            })
5469        })?;
5470
5471        let workspace = if let Some(existing_window) = existing_window {
5472            existing_window
5473        } else {
5474            let project = Project::hosted(
5475                hosted_project_id,
5476                app_state.user_store.clone(),
5477                app_state.client.clone(),
5478                app_state.languages.clone(),
5479                app_state.fs.clone(),
5480                cx.clone(),
5481            )
5482            .await?;
5483
5484            let window_bounds_override = window_bounds_env_override();
5485            cx.update(|cx| {
5486                let mut options = (app_state.build_window_options)(None, cx);
5487                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
5488                cx.open_window(options, |cx| {
5489                    cx.new_view(|cx| {
5490                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5491                    })
5492                })
5493            })??
5494        };
5495
5496        workspace.update(&mut cx, |_, cx| {
5497            cx.activate(true);
5498            cx.activate_window();
5499        })?;
5500
5501        Ok(())
5502    })
5503}
5504
5505pub fn join_dev_server_project(
5506    dev_server_project_id: DevServerProjectId,
5507    project_id: ProjectId,
5508    app_state: Arc<AppState>,
5509    window_to_replace: Option<WindowHandle<Workspace>>,
5510    cx: &mut AppContext,
5511) -> Task<Result<WindowHandle<Workspace>>> {
5512    let windows = cx.windows();
5513    cx.spawn(|mut cx| async move {
5514        let existing_workspace = windows.into_iter().find_map(|window| {
5515            window.downcast::<Workspace>().and_then(|window| {
5516                window
5517                    .update(&mut cx, |workspace, cx| {
5518                        if workspace.project().read(cx).remote_id() == Some(project_id.0) {
5519                            Some(window)
5520                        } else {
5521                            None
5522                        }
5523                    })
5524                    .unwrap_or(None)
5525            })
5526        });
5527
5528        let workspace = if let Some(existing_workspace) = existing_workspace {
5529            existing_workspace
5530        } else {
5531            let project = Project::remote(
5532                project_id.0,
5533                app_state.client.clone(),
5534                app_state.user_store.clone(),
5535                app_state.languages.clone(),
5536                app_state.fs.clone(),
5537                cx.clone(),
5538            )
5539            .await?;
5540
5541            let serialized_workspace: Option<SerializedWorkspace> =
5542                persistence::DB.workspace_for_dev_server_project(dev_server_project_id);
5543
5544            let workspace_id = if let Some(serialized_workspace) = serialized_workspace {
5545                serialized_workspace.id
5546            } else {
5547                persistence::DB.next_id().await?
5548            };
5549
5550            if let Some(window_to_replace) = window_to_replace {
5551                cx.update_window(window_to_replace.into(), |_, cx| {
5552                    cx.replace_root_view(|cx| {
5553                        Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5554                    });
5555                })?;
5556                window_to_replace
5557            } else {
5558                let window_bounds_override = window_bounds_env_override();
5559                cx.update(|cx| {
5560                    let mut options = (app_state.build_window_options)(None, cx);
5561                    options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
5562                    cx.open_window(options, |cx| {
5563                        cx.new_view(|cx| {
5564                            Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5565                        })
5566                    })
5567                })??
5568            }
5569        };
5570
5571        workspace.update(&mut cx, |_, cx| {
5572            cx.activate(true);
5573            cx.activate_window();
5574        })?;
5575
5576        anyhow::Ok(workspace)
5577    })
5578}
5579
5580pub fn join_in_room_project(
5581    project_id: u64,
5582    follow_user_id: u64,
5583    app_state: Arc<AppState>,
5584    cx: &mut AppContext,
5585) -> Task<Result<()>> {
5586    let windows = cx.windows();
5587    cx.spawn(|mut cx| async move {
5588        let existing_workspace = windows.into_iter().find_map(|window| {
5589            window.downcast::<Workspace>().and_then(|window| {
5590                window
5591                    .update(&mut cx, |workspace, cx| {
5592                        if workspace.project().read(cx).remote_id() == Some(project_id) {
5593                            Some(window)
5594                        } else {
5595                            None
5596                        }
5597                    })
5598                    .unwrap_or(None)
5599            })
5600        });
5601
5602        let workspace = if let Some(existing_workspace) = existing_workspace {
5603            existing_workspace
5604        } else {
5605            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
5606            let room = active_call
5607                .read_with(&cx, |call, _| call.room().cloned())?
5608                .ok_or_else(|| anyhow!("not in a call"))?;
5609            let project = room
5610                .update(&mut cx, |room, cx| {
5611                    room.join_project(
5612                        project_id,
5613                        app_state.languages.clone(),
5614                        app_state.fs.clone(),
5615                        cx,
5616                    )
5617                })?
5618                .await?;
5619
5620            let window_bounds_override = window_bounds_env_override();
5621            cx.update(|cx| {
5622                let mut options = (app_state.build_window_options)(None, cx);
5623                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
5624                cx.open_window(options, |cx| {
5625                    cx.new_view(|cx| {
5626                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5627                    })
5628                })
5629            })??
5630        };
5631
5632        workspace.update(&mut cx, |workspace, cx| {
5633            cx.activate(true);
5634            cx.activate_window();
5635
5636            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5637                let follow_peer_id = room
5638                    .read(cx)
5639                    .remote_participants()
5640                    .iter()
5641                    .find(|(_, participant)| participant.user.id == follow_user_id)
5642                    .map(|(_, p)| p.peer_id)
5643                    .or_else(|| {
5644                        // If we couldn't follow the given user, follow the host instead.
5645                        let collaborator = workspace
5646                            .project()
5647                            .read(cx)
5648                            .collaborators()
5649                            .values()
5650                            .find(|collaborator| collaborator.replica_id == 0)?;
5651                        Some(collaborator.peer_id)
5652                    });
5653
5654                if let Some(follow_peer_id) = follow_peer_id {
5655                    workspace.follow(follow_peer_id, cx);
5656                }
5657            }
5658        })?;
5659
5660        anyhow::Ok(())
5661    })
5662}
5663
5664pub fn reload(reload: &Reload, cx: &mut AppContext) {
5665    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5666    let mut workspace_windows = cx
5667        .windows()
5668        .into_iter()
5669        .filter_map(|window| window.downcast::<Workspace>())
5670        .collect::<Vec<_>>();
5671
5672    // If multiple windows have unsaved changes, and need a save prompt,
5673    // prompt in the active window before switching to a different window.
5674    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5675
5676    let mut prompt = None;
5677    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5678        prompt = window
5679            .update(cx, |_, cx| {
5680                cx.prompt(
5681                    PromptLevel::Info,
5682                    "Are you sure you want to restart?",
5683                    None,
5684                    &["Restart", "Cancel"],
5685                )
5686            })
5687            .ok();
5688    }
5689
5690    let binary_path = reload.binary_path.clone();
5691    cx.spawn(|mut cx| async move {
5692        if let Some(prompt) = prompt {
5693            let answer = prompt.await?;
5694            if answer != 0 {
5695                return Ok(());
5696            }
5697        }
5698
5699        // If the user cancels any save prompt, then keep the app open.
5700        for window in workspace_windows {
5701            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5702                workspace.prepare_to_close(CloseIntent::Quit, cx)
5703            }) {
5704                if !should_close.await? {
5705                    return Ok(());
5706                }
5707            }
5708        }
5709
5710        cx.update(|cx| cx.restart(binary_path))
5711    })
5712    .detach_and_log_err(cx);
5713}
5714
5715fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
5716    let mut parts = value.split(',');
5717    let x: usize = parts.next()?.parse().ok()?;
5718    let y: usize = parts.next()?.parse().ok()?;
5719    Some(point(px(x as f32), px(y as f32)))
5720}
5721
5722fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
5723    let mut parts = value.split(',');
5724    let width: usize = parts.next()?.parse().ok()?;
5725    let height: usize = parts.next()?.parse().ok()?;
5726    Some(size(px(width as f32), px(height as f32)))
5727}
5728
5729pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext) -> Stateful<Div> {
5730    const BORDER_SIZE: Pixels = px(1.0);
5731    let decorations = cx.window_decorations();
5732
5733    if matches!(decorations, Decorations::Client { .. }) {
5734        cx.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
5735    }
5736
5737    struct GlobalResizeEdge(ResizeEdge);
5738    impl Global for GlobalResizeEdge {}
5739
5740    div()
5741        .id("window-backdrop")
5742        .bg(transparent_black())
5743        .map(|div| match decorations {
5744            Decorations::Server => div,
5745            Decorations::Client { tiling, .. } => div
5746                .when(!(tiling.top || tiling.right), |div| {
5747                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5748                })
5749                .when(!(tiling.top || tiling.left), |div| {
5750                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5751                })
5752                .when(!(tiling.bottom || tiling.right), |div| {
5753                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5754                })
5755                .when(!(tiling.bottom || tiling.left), |div| {
5756                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5757                })
5758                .when(!tiling.top, |div| {
5759                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
5760                })
5761                .when(!tiling.bottom, |div| {
5762                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
5763                })
5764                .when(!tiling.left, |div| {
5765                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
5766                })
5767                .when(!tiling.right, |div| {
5768                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
5769                })
5770                .on_mouse_move(move |e, cx| {
5771                    let size = cx.window_bounds().get_bounds().size;
5772                    let pos = e.position;
5773
5774                    let new_edge =
5775                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
5776
5777                    let edge = cx.try_global::<GlobalResizeEdge>();
5778                    if new_edge != edge.map(|edge| edge.0) {
5779                        cx.window_handle()
5780                            .update(cx, |workspace, cx| cx.notify(workspace.entity_id()))
5781                            .ok();
5782                    }
5783                })
5784                .on_mouse_down(MouseButton::Left, move |e, cx| {
5785                    let size = cx.window_bounds().get_bounds().size;
5786                    let pos = e.position;
5787
5788                    let edge = match resize_edge(
5789                        pos,
5790                        theme::CLIENT_SIDE_DECORATION_SHADOW,
5791                        size,
5792                        tiling,
5793                    ) {
5794                        Some(value) => value,
5795                        None => return,
5796                    };
5797
5798                    cx.start_window_resize(edge);
5799                }),
5800        })
5801        .size_full()
5802        .child(
5803            div()
5804                .cursor(CursorStyle::Arrow)
5805                .map(|div| match decorations {
5806                    Decorations::Server => div,
5807                    Decorations::Client { tiling } => div
5808                        .border_color(cx.theme().colors().border)
5809                        .when(!(tiling.top || tiling.right), |div| {
5810                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5811                        })
5812                        .when(!(tiling.top || tiling.left), |div| {
5813                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5814                        })
5815                        .when(!(tiling.bottom || tiling.right), |div| {
5816                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5817                        })
5818                        .when(!(tiling.bottom || tiling.left), |div| {
5819                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5820                        })
5821                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
5822                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
5823                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
5824                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
5825                        .when(!tiling.is_tiled(), |div| {
5826                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
5827                                color: Hsla {
5828                                    h: 0.,
5829                                    s: 0.,
5830                                    l: 0.,
5831                                    a: 0.4,
5832                                },
5833                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
5834                                spread_radius: px(0.),
5835                                offset: point(px(0.0), px(0.0)),
5836                            }])
5837                        }),
5838                })
5839                .on_mouse_move(|_e, cx| {
5840                    cx.stop_propagation();
5841                })
5842                .size_full()
5843                .child(element),
5844        )
5845        .map(|div| match decorations {
5846            Decorations::Server => div,
5847            Decorations::Client { tiling, .. } => div.child(
5848                canvas(
5849                    |_bounds, cx| {
5850                        cx.insert_hitbox(
5851                            Bounds::new(
5852                                point(px(0.0), px(0.0)),
5853                                cx.window_bounds().get_bounds().size,
5854                            ),
5855                            false,
5856                        )
5857                    },
5858                    move |_bounds, hitbox, cx| {
5859                        let mouse = cx.mouse_position();
5860                        let size = cx.window_bounds().get_bounds().size;
5861                        let Some(edge) =
5862                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
5863                        else {
5864                            return;
5865                        };
5866                        cx.set_global(GlobalResizeEdge(edge));
5867                        cx.set_cursor_style(
5868                            match edge {
5869                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
5870                                ResizeEdge::Left | ResizeEdge::Right => {
5871                                    CursorStyle::ResizeLeftRight
5872                                }
5873                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
5874                                    CursorStyle::ResizeUpLeftDownRight
5875                                }
5876                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
5877                                    CursorStyle::ResizeUpRightDownLeft
5878                                }
5879                            },
5880                            &hitbox,
5881                        );
5882                    },
5883                )
5884                .size_full()
5885                .absolute(),
5886            ),
5887        })
5888}
5889
5890fn resize_edge(
5891    pos: Point<Pixels>,
5892    shadow_size: Pixels,
5893    window_size: Size<Pixels>,
5894    tiling: Tiling,
5895) -> Option<ResizeEdge> {
5896    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
5897    if bounds.contains(&pos) {
5898        return None;
5899    }
5900
5901    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
5902    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
5903    if !tiling.top && top_left_bounds.contains(&pos) {
5904        return Some(ResizeEdge::TopLeft);
5905    }
5906
5907    let top_right_bounds = Bounds::new(
5908        Point::new(window_size.width - corner_size.width, px(0.)),
5909        corner_size,
5910    );
5911    if !tiling.top && top_right_bounds.contains(&pos) {
5912        return Some(ResizeEdge::TopRight);
5913    }
5914
5915    let bottom_left_bounds = Bounds::new(
5916        Point::new(px(0.), window_size.height - corner_size.height),
5917        corner_size,
5918    );
5919    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
5920        return Some(ResizeEdge::BottomLeft);
5921    }
5922
5923    let bottom_right_bounds = Bounds::new(
5924        Point::new(
5925            window_size.width - corner_size.width,
5926            window_size.height - corner_size.height,
5927        ),
5928        corner_size,
5929    );
5930    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
5931        return Some(ResizeEdge::BottomRight);
5932    }
5933
5934    if !tiling.top && pos.y < shadow_size {
5935        Some(ResizeEdge::Top)
5936    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
5937        Some(ResizeEdge::Bottom)
5938    } else if !tiling.left && pos.x < shadow_size {
5939        Some(ResizeEdge::Left)
5940    } else if !tiling.right && pos.x > window_size.width - shadow_size {
5941        Some(ResizeEdge::Right)
5942    } else {
5943        None
5944    }
5945}
5946
5947#[cfg(test)]
5948mod tests {
5949    use std::{cell::RefCell, rc::Rc};
5950
5951    use super::*;
5952    use crate::{
5953        dock::{test::TestPanel, PanelEvent},
5954        item::{
5955            test::{TestItem, TestProjectItem},
5956            ItemEvent,
5957        },
5958    };
5959    use fs::FakeFs;
5960    use gpui::{
5961        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
5962        UpdateGlobal, VisualTestContext,
5963    };
5964    use project::{Project, ProjectEntryId};
5965    use serde_json::json;
5966    use settings::SettingsStore;
5967
5968    #[gpui::test]
5969    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
5970        init_test(cx);
5971
5972        let fs = FakeFs::new(cx.executor());
5973        let project = Project::test(fs, [], cx).await;
5974        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5975
5976        // Adding an item with no ambiguity renders the tab without detail.
5977        let item1 = cx.new_view(|cx| {
5978            let mut item = TestItem::new(cx);
5979            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
5980            item
5981        });
5982        workspace.update(cx, |workspace, cx| {
5983            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
5984        });
5985        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
5986
5987        // Adding an item that creates ambiguity increases the level of detail on
5988        // both tabs.
5989        let item2 = cx.new_view(|cx| {
5990            let mut item = TestItem::new(cx);
5991            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5992            item
5993        });
5994        workspace.update(cx, |workspace, cx| {
5995            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
5996        });
5997        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5998        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5999
6000        // Adding an item that creates ambiguity increases the level of detail only
6001        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
6002        // we stop at the highest detail available.
6003        let item3 = cx.new_view(|cx| {
6004            let mut item = TestItem::new(cx);
6005            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
6006            item
6007        });
6008        workspace.update(cx, |workspace, cx| {
6009            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6010        });
6011        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6012        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6013        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6014    }
6015
6016    #[gpui::test]
6017    async fn test_tracking_active_path(cx: &mut TestAppContext) {
6018        init_test(cx);
6019
6020        let fs = FakeFs::new(cx.executor());
6021        fs.insert_tree(
6022            "/root1",
6023            json!({
6024                "one.txt": "",
6025                "two.txt": "",
6026            }),
6027        )
6028        .await;
6029        fs.insert_tree(
6030            "/root2",
6031            json!({
6032                "three.txt": "",
6033            }),
6034        )
6035        .await;
6036
6037        let project = Project::test(fs, ["root1".as_ref()], cx).await;
6038        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6039        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6040        let worktree_id = project.update(cx, |project, cx| {
6041            project.worktrees(cx).next().unwrap().read(cx).id()
6042        });
6043
6044        let item1 = cx.new_view(|cx| {
6045            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
6046        });
6047        let item2 = cx.new_view(|cx| {
6048            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
6049        });
6050
6051        // Add an item to an empty pane
6052        workspace.update(cx, |workspace, cx| {
6053            workspace.add_item_to_active_pane(Box::new(item1), None, true, cx)
6054        });
6055        project.update(cx, |project, cx| {
6056            assert_eq!(
6057                project.active_entry(),
6058                project
6059                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6060                    .map(|e| e.id)
6061            );
6062        });
6063        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
6064
6065        // Add a second item to a non-empty pane
6066        workspace.update(cx, |workspace, cx| {
6067            workspace.add_item_to_active_pane(Box::new(item2), None, true, cx)
6068        });
6069        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
6070        project.update(cx, |project, cx| {
6071            assert_eq!(
6072                project.active_entry(),
6073                project
6074                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
6075                    .map(|e| e.id)
6076            );
6077        });
6078
6079        // Close the active item
6080        pane.update(cx, |pane, cx| {
6081            pane.close_active_item(&Default::default(), cx).unwrap()
6082        })
6083        .await
6084        .unwrap();
6085        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
6086        project.update(cx, |project, cx| {
6087            assert_eq!(
6088                project.active_entry(),
6089                project
6090                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6091                    .map(|e| e.id)
6092            );
6093        });
6094
6095        // Add a project folder
6096        project
6097            .update(cx, |project, cx| {
6098                project.find_or_create_worktree("root2", true, cx)
6099            })
6100            .await
6101            .unwrap();
6102        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
6103
6104        // Remove a project folder
6105        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
6106        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
6107    }
6108
6109    #[gpui::test]
6110    async fn test_close_window(cx: &mut TestAppContext) {
6111        init_test(cx);
6112
6113        let fs = FakeFs::new(cx.executor());
6114        fs.insert_tree("/root", json!({ "one": "" })).await;
6115
6116        let project = Project::test(fs, ["root".as_ref()], cx).await;
6117        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6118
6119        // When there are no dirty items, there's nothing to do.
6120        let item1 = cx.new_view(TestItem::new);
6121        workspace.update(cx, |w, cx| {
6122            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx)
6123        });
6124        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6125        assert!(task.await.unwrap());
6126
6127        // When there are dirty untitled items, prompt to save each one. If the user
6128        // cancels any prompt, then abort.
6129        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
6130        let item3 = cx.new_view(|cx| {
6131            TestItem::new(cx)
6132                .with_dirty(true)
6133                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6134        });
6135        workspace.update(cx, |w, cx| {
6136            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6137            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6138        });
6139        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6140        cx.executor().run_until_parked();
6141        cx.simulate_prompt_answer(2); // cancel save all
6142        cx.executor().run_until_parked();
6143        cx.simulate_prompt_answer(2); // cancel save all
6144        cx.executor().run_until_parked();
6145        assert!(!cx.has_pending_prompt());
6146        assert!(!task.await.unwrap());
6147    }
6148
6149    #[gpui::test]
6150    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
6151        init_test(cx);
6152
6153        // Register TestItem as a serializable item
6154        cx.update(|cx| {
6155            register_serializable_item::<TestItem>(cx);
6156        });
6157
6158        let fs = FakeFs::new(cx.executor());
6159        fs.insert_tree("/root", json!({ "one": "" })).await;
6160
6161        let project = Project::test(fs, ["root".as_ref()], cx).await;
6162        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6163
6164        // When there are dirty untitled items, but they can serialize, then there is no prompt.
6165        let item1 = cx.new_view(|cx| {
6166            TestItem::new(cx)
6167                .with_dirty(true)
6168                .with_serialize(|| Some(Task::ready(Ok(()))))
6169        });
6170        let item2 = cx.new_view(|cx| {
6171            TestItem::new(cx)
6172                .with_dirty(true)
6173                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6174                .with_serialize(|| Some(Task::ready(Ok(()))))
6175        });
6176        workspace.update(cx, |w, cx| {
6177            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6178            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6179        });
6180        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6181        assert!(task.await.unwrap());
6182    }
6183
6184    #[gpui::test]
6185    async fn test_close_pane_items(cx: &mut TestAppContext) {
6186        init_test(cx);
6187
6188        let fs = FakeFs::new(cx.executor());
6189
6190        let project = Project::test(fs, None, cx).await;
6191        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6192
6193        let item1 = cx.new_view(|cx| {
6194            TestItem::new(cx)
6195                .with_dirty(true)
6196                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6197        });
6198        let item2 = cx.new_view(|cx| {
6199            TestItem::new(cx)
6200                .with_dirty(true)
6201                .with_conflict(true)
6202                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
6203        });
6204        let item3 = cx.new_view(|cx| {
6205            TestItem::new(cx)
6206                .with_dirty(true)
6207                .with_conflict(true)
6208                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
6209        });
6210        let item4 = cx.new_view(|cx| {
6211            TestItem::new(cx)
6212                .with_dirty(true)
6213                .with_project_items(&[TestProjectItem::new_untitled(cx)])
6214        });
6215        let pane = workspace.update(cx, |workspace, cx| {
6216            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6217            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6218            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6219            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, cx);
6220            workspace.active_pane().clone()
6221        });
6222
6223        let close_items = pane.update(cx, |pane, cx| {
6224            pane.activate_item(1, true, true, cx);
6225            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6226            let item1_id = item1.item_id();
6227            let item3_id = item3.item_id();
6228            let item4_id = item4.item_id();
6229            pane.close_items(cx, SaveIntent::Close, move |id| {
6230                [item1_id, item3_id, item4_id].contains(&id)
6231            })
6232        });
6233        cx.executor().run_until_parked();
6234
6235        assert!(cx.has_pending_prompt());
6236        // Ignore "Save all" prompt
6237        cx.simulate_prompt_answer(2);
6238        cx.executor().run_until_parked();
6239        // There's a prompt to save item 1.
6240        pane.update(cx, |pane, _| {
6241            assert_eq!(pane.items_len(), 4);
6242            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
6243        });
6244        // Confirm saving item 1.
6245        cx.simulate_prompt_answer(0);
6246        cx.executor().run_until_parked();
6247
6248        // Item 1 is saved. There's a prompt to save item 3.
6249        pane.update(cx, |pane, cx| {
6250            assert_eq!(item1.read(cx).save_count, 1);
6251            assert_eq!(item1.read(cx).save_as_count, 0);
6252            assert_eq!(item1.read(cx).reload_count, 0);
6253            assert_eq!(pane.items_len(), 3);
6254            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
6255        });
6256        assert!(cx.has_pending_prompt());
6257
6258        // Cancel saving item 3.
6259        cx.simulate_prompt_answer(1);
6260        cx.executor().run_until_parked();
6261
6262        // Item 3 is reloaded. There's a prompt to save item 4.
6263        pane.update(cx, |pane, cx| {
6264            assert_eq!(item3.read(cx).save_count, 0);
6265            assert_eq!(item3.read(cx).save_as_count, 0);
6266            assert_eq!(item3.read(cx).reload_count, 1);
6267            assert_eq!(pane.items_len(), 2);
6268            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
6269        });
6270        assert!(cx.has_pending_prompt());
6271
6272        // Confirm saving item 4.
6273        cx.simulate_prompt_answer(0);
6274        cx.executor().run_until_parked();
6275
6276        // There's a prompt for a path for item 4.
6277        cx.simulate_new_path_selection(|_| Some(Default::default()));
6278        close_items.await.unwrap();
6279
6280        // The requested items are closed.
6281        pane.update(cx, |pane, cx| {
6282            assert_eq!(item4.read(cx).save_count, 0);
6283            assert_eq!(item4.read(cx).save_as_count, 1);
6284            assert_eq!(item4.read(cx).reload_count, 0);
6285            assert_eq!(pane.items_len(), 1);
6286            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6287        });
6288    }
6289
6290    #[gpui::test]
6291    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
6292        init_test(cx);
6293
6294        let fs = FakeFs::new(cx.executor());
6295        let project = Project::test(fs, [], cx).await;
6296        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6297
6298        // Create several workspace items with single project entries, and two
6299        // workspace items with multiple project entries.
6300        let single_entry_items = (0..=4)
6301            .map(|project_entry_id| {
6302                cx.new_view(|cx| {
6303                    TestItem::new(cx)
6304                        .with_dirty(true)
6305                        .with_project_items(&[TestProjectItem::new(
6306                            project_entry_id,
6307                            &format!("{project_entry_id}.txt"),
6308                            cx,
6309                        )])
6310                })
6311            })
6312            .collect::<Vec<_>>();
6313        let item_2_3 = cx.new_view(|cx| {
6314            TestItem::new(cx)
6315                .with_dirty(true)
6316                .with_singleton(false)
6317                .with_project_items(&[
6318                    single_entry_items[2].read(cx).project_items[0].clone(),
6319                    single_entry_items[3].read(cx).project_items[0].clone(),
6320                ])
6321        });
6322        let item_3_4 = cx.new_view(|cx| {
6323            TestItem::new(cx)
6324                .with_dirty(true)
6325                .with_singleton(false)
6326                .with_project_items(&[
6327                    single_entry_items[3].read(cx).project_items[0].clone(),
6328                    single_entry_items[4].read(cx).project_items[0].clone(),
6329                ])
6330        });
6331
6332        // Create two panes that contain the following project entries:
6333        //   left pane:
6334        //     multi-entry items:   (2, 3)
6335        //     single-entry items:  0, 1, 2, 3, 4
6336        //   right pane:
6337        //     single-entry items:  1
6338        //     multi-entry items:   (3, 4)
6339        let left_pane = workspace.update(cx, |workspace, cx| {
6340            let left_pane = workspace.active_pane().clone();
6341            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, cx);
6342            for item in single_entry_items {
6343                workspace.add_item_to_active_pane(Box::new(item), None, true, cx);
6344            }
6345            left_pane.update(cx, |pane, cx| {
6346                pane.activate_item(2, true, true, cx);
6347            });
6348
6349            let right_pane = workspace
6350                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
6351                .unwrap();
6352
6353            right_pane.update(cx, |pane, cx| {
6354                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
6355            });
6356
6357            left_pane
6358        });
6359
6360        cx.focus_view(&left_pane);
6361
6362        // When closing all of the items in the left pane, we should be prompted twice:
6363        // once for project entry 0, and once for project entry 2. Project entries 1,
6364        // 3, and 4 are all still open in the other paten. After those two
6365        // prompts, the task should complete.
6366
6367        let close = left_pane.update(cx, |pane, cx| {
6368            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
6369        });
6370        cx.executor().run_until_parked();
6371
6372        // Discard "Save all" prompt
6373        cx.simulate_prompt_answer(2);
6374
6375        cx.executor().run_until_parked();
6376        left_pane.update(cx, |pane, cx| {
6377            assert_eq!(
6378                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6379                &[ProjectEntryId::from_proto(0)]
6380            );
6381        });
6382        cx.simulate_prompt_answer(0);
6383
6384        cx.executor().run_until_parked();
6385        left_pane.update(cx, |pane, cx| {
6386            assert_eq!(
6387                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6388                &[ProjectEntryId::from_proto(2)]
6389            );
6390        });
6391        cx.simulate_prompt_answer(0);
6392
6393        cx.executor().run_until_parked();
6394        close.await.unwrap();
6395        left_pane.update(cx, |pane, _| {
6396            assert_eq!(pane.items_len(), 0);
6397        });
6398    }
6399
6400    #[gpui::test]
6401    async fn test_autosave(cx: &mut gpui::TestAppContext) {
6402        init_test(cx);
6403
6404        let fs = FakeFs::new(cx.executor());
6405        let project = Project::test(fs, [], cx).await;
6406        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6407        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6408
6409        let item = cx.new_view(|cx| {
6410            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6411        });
6412        let item_id = item.entity_id();
6413        workspace.update(cx, |workspace, cx| {
6414            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6415        });
6416
6417        // Autosave on window change.
6418        item.update(cx, |item, cx| {
6419            SettingsStore::update_global(cx, |settings, cx| {
6420                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6421                    settings.autosave = AutosaveSetting::OnWindowChange;
6422                })
6423            });
6424            item.is_dirty = true;
6425        });
6426
6427        // Deactivating the window saves the file.
6428        cx.deactivate_window();
6429        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6430
6431        // Re-activating the window doesn't save the file.
6432        cx.update(|cx| cx.activate_window());
6433        cx.executor().run_until_parked();
6434        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6435
6436        // Autosave on focus change.
6437        item.update(cx, |item, cx| {
6438            cx.focus_self();
6439            SettingsStore::update_global(cx, |settings, cx| {
6440                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6441                    settings.autosave = AutosaveSetting::OnFocusChange;
6442                })
6443            });
6444            item.is_dirty = true;
6445        });
6446
6447        // Blurring the item saves the file.
6448        item.update(cx, |_, cx| cx.blur());
6449        cx.executor().run_until_parked();
6450        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
6451
6452        // Deactivating the window still saves the file.
6453        item.update(cx, |item, cx| {
6454            cx.focus_self();
6455            item.is_dirty = true;
6456        });
6457        cx.deactivate_window();
6458        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6459
6460        // Autosave after delay.
6461        item.update(cx, |item, cx| {
6462            SettingsStore::update_global(cx, |settings, cx| {
6463                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6464                    settings.autosave = AutosaveSetting::AfterDelay { milliseconds: 500 };
6465                })
6466            });
6467            item.is_dirty = true;
6468            cx.emit(ItemEvent::Edit);
6469        });
6470
6471        // Delay hasn't fully expired, so the file is still dirty and unsaved.
6472        cx.executor().advance_clock(Duration::from_millis(250));
6473        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6474
6475        // After delay expires, the file is saved.
6476        cx.executor().advance_clock(Duration::from_millis(250));
6477        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
6478
6479        // Autosave on focus change, ensuring closing the tab counts as such.
6480        item.update(cx, |item, cx| {
6481            SettingsStore::update_global(cx, |settings, cx| {
6482                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6483                    settings.autosave = AutosaveSetting::OnFocusChange;
6484                })
6485            });
6486            item.is_dirty = true;
6487        });
6488
6489        pane.update(cx, |pane, cx| {
6490            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6491        })
6492        .await
6493        .unwrap();
6494        assert!(!cx.has_pending_prompt());
6495        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6496
6497        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
6498        workspace.update(cx, |workspace, cx| {
6499            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6500        });
6501        item.update(cx, |item, cx| {
6502            item.project_items[0].update(cx, |item, _| {
6503                item.entry_id = None;
6504            });
6505            item.is_dirty = true;
6506            cx.blur();
6507        });
6508        cx.run_until_parked();
6509        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6510
6511        // Ensure autosave is prevented for deleted files also when closing the buffer.
6512        let _close_items = pane.update(cx, |pane, cx| {
6513            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6514        });
6515        cx.run_until_parked();
6516        assert!(cx.has_pending_prompt());
6517        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6518    }
6519
6520    #[gpui::test]
6521    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
6522        init_test(cx);
6523
6524        let fs = FakeFs::new(cx.executor());
6525
6526        let project = Project::test(fs, [], cx).await;
6527        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6528
6529        let item = cx.new_view(|cx| {
6530            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6531        });
6532        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6533        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
6534        let toolbar_notify_count = Rc::new(RefCell::new(0));
6535
6536        workspace.update(cx, |workspace, cx| {
6537            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6538            let toolbar_notification_count = toolbar_notify_count.clone();
6539            cx.observe(&toolbar, move |_, _, _| {
6540                *toolbar_notification_count.borrow_mut() += 1
6541            })
6542            .detach();
6543        });
6544
6545        pane.update(cx, |pane, _| {
6546            assert!(!pane.can_navigate_backward());
6547            assert!(!pane.can_navigate_forward());
6548        });
6549
6550        item.update(cx, |item, cx| {
6551            item.set_state("one".to_string(), cx);
6552        });
6553
6554        // Toolbar must be notified to re-render the navigation buttons
6555        assert_eq!(*toolbar_notify_count.borrow(), 1);
6556
6557        pane.update(cx, |pane, _| {
6558            assert!(pane.can_navigate_backward());
6559            assert!(!pane.can_navigate_forward());
6560        });
6561
6562        workspace
6563            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
6564            .await
6565            .unwrap();
6566
6567        assert_eq!(*toolbar_notify_count.borrow(), 2);
6568        pane.update(cx, |pane, _| {
6569            assert!(!pane.can_navigate_backward());
6570            assert!(pane.can_navigate_forward());
6571        });
6572    }
6573
6574    #[gpui::test]
6575    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
6576        init_test(cx);
6577        let fs = FakeFs::new(cx.executor());
6578
6579        let project = Project::test(fs, [], cx).await;
6580        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6581
6582        let panel = workspace.update(cx, |workspace, cx| {
6583            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6584            workspace.add_panel(panel.clone(), cx);
6585
6586            workspace
6587                .right_dock()
6588                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6589
6590            panel
6591        });
6592
6593        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6594        pane.update(cx, |pane, cx| {
6595            let item = cx.new_view(TestItem::new);
6596            pane.add_item(Box::new(item), true, true, None, cx);
6597        });
6598
6599        // Transfer focus from center to panel
6600        workspace.update(cx, |workspace, cx| {
6601            workspace.toggle_panel_focus::<TestPanel>(cx);
6602        });
6603
6604        workspace.update(cx, |workspace, cx| {
6605            assert!(workspace.right_dock().read(cx).is_open());
6606            assert!(!panel.is_zoomed(cx));
6607            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6608        });
6609
6610        // Transfer focus from panel to center
6611        workspace.update(cx, |workspace, cx| {
6612            workspace.toggle_panel_focus::<TestPanel>(cx);
6613        });
6614
6615        workspace.update(cx, |workspace, cx| {
6616            assert!(workspace.right_dock().read(cx).is_open());
6617            assert!(!panel.is_zoomed(cx));
6618            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6619        });
6620
6621        // Close the dock
6622        workspace.update(cx, |workspace, cx| {
6623            workspace.toggle_dock(DockPosition::Right, cx);
6624        });
6625
6626        workspace.update(cx, |workspace, cx| {
6627            assert!(!workspace.right_dock().read(cx).is_open());
6628            assert!(!panel.is_zoomed(cx));
6629            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6630        });
6631
6632        // Open the dock
6633        workspace.update(cx, |workspace, cx| {
6634            workspace.toggle_dock(DockPosition::Right, cx);
6635        });
6636
6637        workspace.update(cx, |workspace, cx| {
6638            assert!(workspace.right_dock().read(cx).is_open());
6639            assert!(!panel.is_zoomed(cx));
6640            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6641        });
6642
6643        // Focus and zoom panel
6644        panel.update(cx, |panel, cx| {
6645            cx.focus_self();
6646            panel.set_zoomed(true, cx)
6647        });
6648
6649        workspace.update(cx, |workspace, cx| {
6650            assert!(workspace.right_dock().read(cx).is_open());
6651            assert!(panel.is_zoomed(cx));
6652            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6653        });
6654
6655        // Transfer focus to the center closes the dock
6656        workspace.update(cx, |workspace, cx| {
6657            workspace.toggle_panel_focus::<TestPanel>(cx);
6658        });
6659
6660        workspace.update(cx, |workspace, cx| {
6661            assert!(!workspace.right_dock().read(cx).is_open());
6662            assert!(panel.is_zoomed(cx));
6663            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6664        });
6665
6666        // Transferring focus back to the panel keeps it zoomed
6667        workspace.update(cx, |workspace, cx| {
6668            workspace.toggle_panel_focus::<TestPanel>(cx);
6669        });
6670
6671        workspace.update(cx, |workspace, cx| {
6672            assert!(workspace.right_dock().read(cx).is_open());
6673            assert!(panel.is_zoomed(cx));
6674            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6675        });
6676
6677        // Close the dock while it is zoomed
6678        workspace.update(cx, |workspace, cx| {
6679            workspace.toggle_dock(DockPosition::Right, cx)
6680        });
6681
6682        workspace.update(cx, |workspace, cx| {
6683            assert!(!workspace.right_dock().read(cx).is_open());
6684            assert!(panel.is_zoomed(cx));
6685            assert!(workspace.zoomed.is_none());
6686            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6687        });
6688
6689        // Opening the dock, when it's zoomed, retains focus
6690        workspace.update(cx, |workspace, cx| {
6691            workspace.toggle_dock(DockPosition::Right, cx)
6692        });
6693
6694        workspace.update(cx, |workspace, cx| {
6695            assert!(workspace.right_dock().read(cx).is_open());
6696            assert!(panel.is_zoomed(cx));
6697            assert!(workspace.zoomed.is_some());
6698            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6699        });
6700
6701        // Unzoom and close the panel, zoom the active pane.
6702        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
6703        workspace.update(cx, |workspace, cx| {
6704            workspace.toggle_dock(DockPosition::Right, cx)
6705        });
6706        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
6707
6708        // Opening a dock unzooms the pane.
6709        workspace.update(cx, |workspace, cx| {
6710            workspace.toggle_dock(DockPosition::Right, cx)
6711        });
6712        workspace.update(cx, |workspace, cx| {
6713            let pane = pane.read(cx);
6714            assert!(!pane.is_zoomed());
6715            assert!(!pane.focus_handle(cx).is_focused(cx));
6716            assert!(workspace.right_dock().read(cx).is_open());
6717            assert!(workspace.zoomed.is_none());
6718        });
6719    }
6720
6721    #[gpui::test]
6722    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
6723        init_test(cx);
6724
6725        let fs = FakeFs::new(cx.executor());
6726
6727        let project = Project::test(fs, None, cx).await;
6728        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6729
6730        // Let's arrange the panes like this:
6731        //
6732        // +-----------------------+
6733        // |         top           |
6734        // +------+--------+-------+
6735        // | left | center | right |
6736        // +------+--------+-------+
6737        // |        bottom         |
6738        // +-----------------------+
6739
6740        let top_item = cx.new_view(|cx| {
6741            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
6742        });
6743        let bottom_item = cx.new_view(|cx| {
6744            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
6745        });
6746        let left_item = cx.new_view(|cx| {
6747            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
6748        });
6749        let right_item = cx.new_view(|cx| {
6750            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
6751        });
6752        let center_item = cx.new_view(|cx| {
6753            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
6754        });
6755
6756        let top_pane_id = workspace.update(cx, |workspace, cx| {
6757            let top_pane_id = workspace.active_pane().entity_id();
6758            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, cx);
6759            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Down, cx);
6760            top_pane_id
6761        });
6762        let bottom_pane_id = workspace.update(cx, |workspace, cx| {
6763            let bottom_pane_id = workspace.active_pane().entity_id();
6764            workspace.add_item_to_active_pane(Box::new(bottom_item.clone()), None, false, cx);
6765            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Up, cx);
6766            bottom_pane_id
6767        });
6768        let left_pane_id = workspace.update(cx, |workspace, cx| {
6769            let left_pane_id = workspace.active_pane().entity_id();
6770            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, cx);
6771            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
6772            left_pane_id
6773        });
6774        let right_pane_id = workspace.update(cx, |workspace, cx| {
6775            let right_pane_id = workspace.active_pane().entity_id();
6776            workspace.add_item_to_active_pane(Box::new(right_item.clone()), None, false, cx);
6777            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Left, cx);
6778            right_pane_id
6779        });
6780        let center_pane_id = workspace.update(cx, |workspace, cx| {
6781            let center_pane_id = workspace.active_pane().entity_id();
6782            workspace.add_item_to_active_pane(Box::new(center_item.clone()), None, false, cx);
6783            center_pane_id
6784        });
6785        cx.executor().run_until_parked();
6786
6787        workspace.update(cx, |workspace, cx| {
6788            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
6789
6790            // Join into next from center pane into right
6791            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6792        });
6793
6794        workspace.update(cx, |workspace, cx| {
6795            let active_pane = workspace.active_pane();
6796            assert_eq!(right_pane_id, active_pane.entity_id());
6797            assert_eq!(2, active_pane.read(cx).items_len());
6798            let item_ids_in_pane =
6799                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6800            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6801            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6802
6803            // Join into next from right pane into bottom
6804            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6805        });
6806
6807        workspace.update(cx, |workspace, cx| {
6808            let active_pane = workspace.active_pane();
6809            assert_eq!(bottom_pane_id, active_pane.entity_id());
6810            assert_eq!(3, active_pane.read(cx).items_len());
6811            let item_ids_in_pane =
6812                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6813            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6814            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6815            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
6816
6817            // Join into next from bottom pane into left
6818            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6819        });
6820
6821        workspace.update(cx, |workspace, cx| {
6822            let active_pane = workspace.active_pane();
6823            assert_eq!(left_pane_id, active_pane.entity_id());
6824            assert_eq!(4, active_pane.read(cx).items_len());
6825            let item_ids_in_pane =
6826                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6827            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6828            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6829            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
6830            assert!(item_ids_in_pane.contains(&left_item.item_id()));
6831
6832            // Join into next from left pane into top
6833            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
6834        });
6835
6836        workspace.update(cx, |workspace, cx| {
6837            let active_pane = workspace.active_pane();
6838            assert_eq!(top_pane_id, active_pane.entity_id());
6839            assert_eq!(5, active_pane.read(cx).items_len());
6840            let item_ids_in_pane =
6841                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
6842            assert!(item_ids_in_pane.contains(&center_item.item_id()));
6843            assert!(item_ids_in_pane.contains(&right_item.item_id()));
6844            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
6845            assert!(item_ids_in_pane.contains(&left_item.item_id()));
6846            assert!(item_ids_in_pane.contains(&top_item.item_id()));
6847
6848            // Single pane left: no-op
6849            workspace.join_pane_into_next(workspace.active_pane().clone(), cx)
6850        });
6851
6852        workspace.update(cx, |workspace, _cx| {
6853            let active_pane = workspace.active_pane();
6854            assert_eq!(top_pane_id, active_pane.entity_id());
6855        });
6856    }
6857
6858    struct TestModal(FocusHandle);
6859
6860    impl TestModal {
6861        fn new(cx: &mut ViewContext<Self>) -> Self {
6862            Self(cx.focus_handle())
6863        }
6864    }
6865
6866    impl EventEmitter<DismissEvent> for TestModal {}
6867
6868    impl FocusableView for TestModal {
6869        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6870            self.0.clone()
6871        }
6872    }
6873
6874    impl ModalView for TestModal {}
6875
6876    impl Render for TestModal {
6877        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
6878            div().track_focus(&self.0)
6879        }
6880    }
6881
6882    #[gpui::test]
6883    async fn test_panels(cx: &mut gpui::TestAppContext) {
6884        init_test(cx);
6885        let fs = FakeFs::new(cx.executor());
6886
6887        let project = Project::test(fs, [], cx).await;
6888        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6889
6890        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
6891            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
6892            workspace.add_panel(panel_1.clone(), cx);
6893            workspace
6894                .left_dock()
6895                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
6896            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6897            workspace.add_panel(panel_2.clone(), cx);
6898            workspace
6899                .right_dock()
6900                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6901
6902            let left_dock = workspace.left_dock();
6903            assert_eq!(
6904                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6905                panel_1.panel_id()
6906            );
6907            assert_eq!(
6908                left_dock.read(cx).active_panel_size(cx).unwrap(),
6909                panel_1.size(cx)
6910            );
6911
6912            left_dock.update(cx, |left_dock, cx| {
6913                left_dock.resize_active_panel(Some(px(1337.)), cx)
6914            });
6915            assert_eq!(
6916                workspace
6917                    .right_dock()
6918                    .read(cx)
6919                    .visible_panel()
6920                    .unwrap()
6921                    .panel_id(),
6922                panel_2.panel_id(),
6923            );
6924
6925            (panel_1, panel_2)
6926        });
6927
6928        // Move panel_1 to the right
6929        panel_1.update(cx, |panel_1, cx| {
6930            panel_1.set_position(DockPosition::Right, cx)
6931        });
6932
6933        workspace.update(cx, |workspace, cx| {
6934            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
6935            // Since it was the only panel on the left, the left dock should now be closed.
6936            assert!(!workspace.left_dock().read(cx).is_open());
6937            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
6938            let right_dock = workspace.right_dock();
6939            assert_eq!(
6940                right_dock.read(cx).visible_panel().unwrap().panel_id(),
6941                panel_1.panel_id()
6942            );
6943            assert_eq!(
6944                right_dock.read(cx).active_panel_size(cx).unwrap(),
6945                px(1337.)
6946            );
6947
6948            // Now we move panel_2 to the left
6949            panel_2.set_position(DockPosition::Left, cx);
6950        });
6951
6952        workspace.update(cx, |workspace, cx| {
6953            // Since panel_2 was not visible on the right, we don't open the left dock.
6954            assert!(!workspace.left_dock().read(cx).is_open());
6955            // And the right dock is unaffected in its displaying of panel_1
6956            assert!(workspace.right_dock().read(cx).is_open());
6957            assert_eq!(
6958                workspace
6959                    .right_dock()
6960                    .read(cx)
6961                    .visible_panel()
6962                    .unwrap()
6963                    .panel_id(),
6964                panel_1.panel_id(),
6965            );
6966        });
6967
6968        // Move panel_1 back to the left
6969        panel_1.update(cx, |panel_1, cx| {
6970            panel_1.set_position(DockPosition::Left, cx)
6971        });
6972
6973        workspace.update(cx, |workspace, cx| {
6974            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
6975            let left_dock = workspace.left_dock();
6976            assert!(left_dock.read(cx).is_open());
6977            assert_eq!(
6978                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6979                panel_1.panel_id()
6980            );
6981            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
6982            // And the right dock should be closed as it no longer has any panels.
6983            assert!(!workspace.right_dock().read(cx).is_open());
6984
6985            // Now we move panel_1 to the bottom
6986            panel_1.set_position(DockPosition::Bottom, cx);
6987        });
6988
6989        workspace.update(cx, |workspace, cx| {
6990            // Since panel_1 was visible on the left, we close the left dock.
6991            assert!(!workspace.left_dock().read(cx).is_open());
6992            // The bottom dock is sized based on the panel's default size,
6993            // since the panel orientation changed from vertical to horizontal.
6994            let bottom_dock = workspace.bottom_dock();
6995            assert_eq!(
6996                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
6997                panel_1.size(cx),
6998            );
6999            // Close bottom dock and move panel_1 back to the left.
7000            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
7001            panel_1.set_position(DockPosition::Left, cx);
7002        });
7003
7004        // Emit activated event on panel 1
7005        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
7006
7007        // Now the left dock is open and panel_1 is active and focused.
7008        workspace.update(cx, |workspace, cx| {
7009            let left_dock = workspace.left_dock();
7010            assert!(left_dock.read(cx).is_open());
7011            assert_eq!(
7012                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7013                panel_1.panel_id(),
7014            );
7015            assert!(panel_1.focus_handle(cx).is_focused(cx));
7016        });
7017
7018        // Emit closed event on panel 2, which is not active
7019        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7020
7021        // Wo don't close the left dock, because panel_2 wasn't the active panel
7022        workspace.update(cx, |workspace, cx| {
7023            let left_dock = workspace.left_dock();
7024            assert!(left_dock.read(cx).is_open());
7025            assert_eq!(
7026                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7027                panel_1.panel_id(),
7028            );
7029        });
7030
7031        // Emitting a ZoomIn event shows the panel as zoomed.
7032        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
7033        workspace.update(cx, |workspace, _| {
7034            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7035            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
7036        });
7037
7038        // Move panel to another dock while it is zoomed
7039        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
7040        workspace.update(cx, |workspace, _| {
7041            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7042
7043            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7044        });
7045
7046        // This is a helper for getting a:
7047        // - valid focus on an element,
7048        // - that isn't a part of the panes and panels system of the Workspace,
7049        // - and doesn't trigger the 'on_focus_lost' API.
7050        let focus_other_view = {
7051            let workspace = workspace.clone();
7052            move |cx: &mut VisualTestContext| {
7053                workspace.update(cx, |workspace, cx| {
7054                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
7055                        workspace.toggle_modal(cx, TestModal::new);
7056                        workspace.toggle_modal(cx, TestModal::new);
7057                    } else {
7058                        workspace.toggle_modal(cx, TestModal::new);
7059                    }
7060                })
7061            }
7062        };
7063
7064        // If focus is transferred to another view that's not a panel or another pane, we still show
7065        // the panel as zoomed.
7066        focus_other_view(cx);
7067        workspace.update(cx, |workspace, _| {
7068            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7069            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7070        });
7071
7072        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
7073        workspace.update(cx, |_, cx| cx.focus_self());
7074        workspace.update(cx, |workspace, _| {
7075            assert_eq!(workspace.zoomed, None);
7076            assert_eq!(workspace.zoomed_position, None);
7077        });
7078
7079        // If focus is transferred again to another view that's not a panel or a pane, we won't
7080        // show the panel as zoomed because it wasn't zoomed before.
7081        focus_other_view(cx);
7082        workspace.update(cx, |workspace, _| {
7083            assert_eq!(workspace.zoomed, None);
7084            assert_eq!(workspace.zoomed_position, None);
7085        });
7086
7087        // When the panel is activated, it is zoomed again.
7088        cx.dispatch_action(ToggleRightDock);
7089        workspace.update(cx, |workspace, _| {
7090            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7091            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7092        });
7093
7094        // Emitting a ZoomOut event unzooms the panel.
7095        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
7096        workspace.update(cx, |workspace, _| {
7097            assert_eq!(workspace.zoomed, None);
7098            assert_eq!(workspace.zoomed_position, None);
7099        });
7100
7101        // Emit closed event on panel 1, which is active
7102        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7103
7104        // Now the left dock is closed, because panel_1 was the active panel
7105        workspace.update(cx, |workspace, cx| {
7106            let right_dock = workspace.right_dock();
7107            assert!(!right_dock.read(cx).is_open());
7108        });
7109    }
7110
7111    mod register_project_item_tests {
7112        use ui::Context as _;
7113
7114        use super::*;
7115
7116        // View
7117        struct TestPngItemView {
7118            focus_handle: FocusHandle,
7119        }
7120        // Model
7121        struct TestPngItem {}
7122
7123        impl project::Item for TestPngItem {
7124            fn try_open(
7125                _project: &Model<Project>,
7126                path: &ProjectPath,
7127                cx: &mut AppContext,
7128            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7129                if path.path.extension().unwrap() == "png" {
7130                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
7131                } else {
7132                    None
7133                }
7134            }
7135
7136            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7137                None
7138            }
7139
7140            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7141                None
7142            }
7143        }
7144
7145        impl Item for TestPngItemView {
7146            type Event = ();
7147        }
7148        impl EventEmitter<()> for TestPngItemView {}
7149        impl FocusableView for TestPngItemView {
7150            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7151                self.focus_handle.clone()
7152            }
7153        }
7154
7155        impl Render for TestPngItemView {
7156            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7157                Empty
7158            }
7159        }
7160
7161        impl ProjectItem for TestPngItemView {
7162            type Item = TestPngItem;
7163
7164            fn for_project_item(
7165                _project: Model<Project>,
7166                _item: Model<Self::Item>,
7167                cx: &mut ViewContext<Self>,
7168            ) -> Self
7169            where
7170                Self: Sized,
7171            {
7172                Self {
7173                    focus_handle: cx.focus_handle(),
7174                }
7175            }
7176        }
7177
7178        // View
7179        struct TestIpynbItemView {
7180            focus_handle: FocusHandle,
7181        }
7182        // Model
7183        struct TestIpynbItem {}
7184
7185        impl project::Item for TestIpynbItem {
7186            fn try_open(
7187                _project: &Model<Project>,
7188                path: &ProjectPath,
7189                cx: &mut AppContext,
7190            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7191                if path.path.extension().unwrap() == "ipynb" {
7192                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
7193                } else {
7194                    None
7195                }
7196            }
7197
7198            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7199                None
7200            }
7201
7202            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7203                None
7204            }
7205        }
7206
7207        impl Item for TestIpynbItemView {
7208            type Event = ();
7209        }
7210        impl EventEmitter<()> for TestIpynbItemView {}
7211        impl FocusableView for TestIpynbItemView {
7212            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7213                self.focus_handle.clone()
7214            }
7215        }
7216
7217        impl Render for TestIpynbItemView {
7218            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7219                Empty
7220            }
7221        }
7222
7223        impl ProjectItem for TestIpynbItemView {
7224            type Item = TestIpynbItem;
7225
7226            fn for_project_item(
7227                _project: Model<Project>,
7228                _item: Model<Self::Item>,
7229                cx: &mut ViewContext<Self>,
7230            ) -> Self
7231            where
7232                Self: Sized,
7233            {
7234                Self {
7235                    focus_handle: cx.focus_handle(),
7236                }
7237            }
7238        }
7239
7240        struct TestAlternatePngItemView {
7241            focus_handle: FocusHandle,
7242        }
7243
7244        impl Item for TestAlternatePngItemView {
7245            type Event = ();
7246        }
7247
7248        impl EventEmitter<()> for TestAlternatePngItemView {}
7249        impl FocusableView for TestAlternatePngItemView {
7250            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7251                self.focus_handle.clone()
7252            }
7253        }
7254
7255        impl Render for TestAlternatePngItemView {
7256            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7257                Empty
7258            }
7259        }
7260
7261        impl ProjectItem for TestAlternatePngItemView {
7262            type Item = TestPngItem;
7263
7264            fn for_project_item(
7265                _project: Model<Project>,
7266                _item: Model<Self::Item>,
7267                cx: &mut ViewContext<Self>,
7268            ) -> Self
7269            where
7270                Self: Sized,
7271            {
7272                Self {
7273                    focus_handle: cx.focus_handle(),
7274                }
7275            }
7276        }
7277
7278        #[gpui::test]
7279        async fn test_register_project_item(cx: &mut TestAppContext) {
7280            init_test(cx);
7281
7282            cx.update(|cx| {
7283                register_project_item::<TestPngItemView>(cx);
7284                register_project_item::<TestIpynbItemView>(cx);
7285            });
7286
7287            let fs = FakeFs::new(cx.executor());
7288            fs.insert_tree(
7289                "/root1",
7290                json!({
7291                    "one.png": "BINARYDATAHERE",
7292                    "two.ipynb": "{ totally a notebook }",
7293                    "three.txt": "editing text, sure why not?"
7294                }),
7295            )
7296            .await;
7297
7298            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7299            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7300
7301            let worktree_id = project.update(cx, |project, cx| {
7302                project.worktrees(cx).next().unwrap().read(cx).id()
7303            });
7304
7305            let handle = workspace
7306                .update(cx, |workspace, cx| {
7307                    let project_path = (worktree_id, "one.png");
7308                    workspace.open_path(project_path, None, true, cx)
7309                })
7310                .await
7311                .unwrap();
7312
7313            // Now we can check if the handle we got back errored or not
7314            assert_eq!(
7315                handle.to_any().entity_type(),
7316                TypeId::of::<TestPngItemView>()
7317            );
7318
7319            let handle = workspace
7320                .update(cx, |workspace, cx| {
7321                    let project_path = (worktree_id, "two.ipynb");
7322                    workspace.open_path(project_path, None, true, cx)
7323                })
7324                .await
7325                .unwrap();
7326
7327            assert_eq!(
7328                handle.to_any().entity_type(),
7329                TypeId::of::<TestIpynbItemView>()
7330            );
7331
7332            let handle = workspace
7333                .update(cx, |workspace, cx| {
7334                    let project_path = (worktree_id, "three.txt");
7335                    workspace.open_path(project_path, None, true, cx)
7336                })
7337                .await;
7338            assert!(handle.is_err());
7339        }
7340
7341        #[gpui::test]
7342        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
7343            init_test(cx);
7344
7345            cx.update(|cx| {
7346                register_project_item::<TestPngItemView>(cx);
7347                register_project_item::<TestAlternatePngItemView>(cx);
7348            });
7349
7350            let fs = FakeFs::new(cx.executor());
7351            fs.insert_tree(
7352                "/root1",
7353                json!({
7354                    "one.png": "BINARYDATAHERE",
7355                    "two.ipynb": "{ totally a notebook }",
7356                    "three.txt": "editing text, sure why not?"
7357                }),
7358            )
7359            .await;
7360
7361            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7362            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7363
7364            let worktree_id = project.update(cx, |project, cx| {
7365                project.worktrees(cx).next().unwrap().read(cx).id()
7366            });
7367
7368            let handle = workspace
7369                .update(cx, |workspace, cx| {
7370                    let project_path = (worktree_id, "one.png");
7371                    workspace.open_path(project_path, None, true, cx)
7372                })
7373                .await
7374                .unwrap();
7375
7376            // This _must_ be the second item registered
7377            assert_eq!(
7378                handle.to_any().entity_type(),
7379                TypeId::of::<TestAlternatePngItemView>()
7380            );
7381
7382            let handle = workspace
7383                .update(cx, |workspace, cx| {
7384                    let project_path = (worktree_id, "three.txt");
7385                    workspace.open_path(project_path, None, true, cx)
7386                })
7387                .await;
7388            assert!(handle.is_err());
7389        }
7390    }
7391
7392    pub fn init_test(cx: &mut TestAppContext) {
7393        cx.update(|cx| {
7394            let settings_store = SettingsStore::test(cx);
7395            cx.set_global(settings_store);
7396            theme::init(theme::LoadThemes::JustBase, cx);
7397            language::init(cx);
7398            crate::init_settings(cx);
7399            Project::init_settings(cx);
7400        });
7401    }
7402}