workspace2.rs

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