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