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