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,
  34    DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels,
  35    InteractiveElement, KeyContext, ManagedView, Model, ModelContext, ParentElement,
  36    PathPromptOptions, Pixels, Point, PromptLevel, Render, Size, Styled, Subscription, Task, View,
  37    ViewContext, 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    init_settings(cx);
 231    notifications::init(cx);
 232
 233    cx.on_action(Workspace::close_global);
 234    cx.on_action(restart);
 235
 236    cx.on_action({
 237        let app_state = Arc::downgrade(&app_state);
 238        move |_: &Open, cx: &mut AppContext| {
 239            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 240                files: true,
 241                directories: true,
 242                multiple: true,
 243            });
 244
 245            if let Some(app_state) = app_state.upgrade() {
 246                cx.spawn(move |mut cx| async move {
 247                    if let Some(paths) = paths.await.log_err().flatten() {
 248                        cx.update(|cx| {
 249                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 250                        })
 251                        .ok();
 252                    }
 253                })
 254                .detach();
 255            }
 256        }
 257    });
 258}
 259
 260type ProjectItemBuilders =
 261    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
 262pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 263    let builders = cx.default_global::<ProjectItemBuilders>();
 264    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 265        let item = model.downcast::<I::Item>().unwrap();
 266        Box::new(cx.build_view(|cx| I::for_project_item(project, item, cx)))
 267    });
 268}
 269
 270type FollowableItemBuilder = fn(
 271    View<Pane>,
 272    View<Workspace>,
 273    ViewId,
 274    &mut Option<proto::view::Variant>,
 275    &mut WindowContext,
 276) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 277type FollowableItemBuilders = HashMap<
 278    TypeId,
 279    (
 280        FollowableItemBuilder,
 281        fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 282    ),
 283>;
 284pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 285    let builders = cx.default_global::<FollowableItemBuilders>();
 286    builders.insert(
 287        TypeId::of::<I>(),
 288        (
 289            |pane, workspace, id, state, cx| {
 290                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 291                    cx.foreground_executor()
 292                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 293                })
 294            },
 295            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 296        ),
 297    );
 298}
 299
 300type ItemDeserializers = HashMap<
 301    Arc<str>,
 302    fn(
 303        Model<Project>,
 304        WeakView<Workspace>,
 305        WorkspaceId,
 306        ItemId,
 307        &mut ViewContext<Pane>,
 308    ) -> Task<Result<Box<dyn ItemHandle>>>,
 309>;
 310pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 311    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 312        let deserializers = cx.default_global::<ItemDeserializers>();
 313        deserializers.insert(
 314            Arc::from(serialized_item_kind),
 315            |project, workspace, workspace_id, item_id, cx| {
 316                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 317                cx.foreground_executor()
 318                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 319            },
 320        );
 321    }
 322}
 323
 324pub struct AppState {
 325    pub languages: Arc<LanguageRegistry>,
 326    pub client: Arc<Client>,
 327    pub user_store: Model<UserStore>,
 328    pub workspace_store: Model<WorkspaceStore>,
 329    pub fs: Arc<dyn fs::Fs>,
 330    pub build_window_options:
 331        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
 332    pub node_runtime: Arc<dyn NodeRuntime>,
 333}
 334
 335pub struct WorkspaceStore {
 336    workspaces: HashSet<WindowHandle<Workspace>>,
 337    followers: Vec<Follower>,
 338    client: Arc<Client>,
 339    _subscriptions: Vec<client::Subscription>,
 340}
 341
 342#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 343struct Follower {
 344    project_id: Option<u64>,
 345    peer_id: PeerId,
 346}
 347
 348impl AppState {
 349    #[cfg(any(test, feature = "test-support"))]
 350    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 351        use node_runtime::FakeNodeRuntime;
 352        use settings::SettingsStore;
 353
 354        if !cx.has_global::<SettingsStore>() {
 355            let settings_store = SettingsStore::test(cx);
 356            cx.set_global(settings_store);
 357        }
 358
 359        let fs = fs::FakeFs::new(cx.background_executor().clone());
 360        let languages = Arc::new(LanguageRegistry::test());
 361        let http_client = util::http::FakeHttpClient::with_404_response();
 362        let client = Client::new(http_client.clone(), cx);
 363        let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
 364        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
 365
 366        theme::init(theme::LoadThemes::JustBase, cx);
 367        client::init(&client, cx);
 368        crate::init_settings(cx);
 369
 370        Arc::new(Self {
 371            client,
 372            fs,
 373            languages,
 374            user_store,
 375            workspace_store,
 376            node_runtime: FakeNodeRuntime::new(),
 377            build_window_options: |_, _, _| Default::default(),
 378        })
 379    }
 380}
 381
 382struct DelayedDebouncedEditAction {
 383    task: Option<Task<()>>,
 384    cancel_channel: Option<oneshot::Sender<()>>,
 385}
 386
 387impl DelayedDebouncedEditAction {
 388    fn new() -> DelayedDebouncedEditAction {
 389        DelayedDebouncedEditAction {
 390            task: None,
 391            cancel_channel: None,
 392        }
 393    }
 394
 395    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 396    where
 397        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 398    {
 399        if let Some(channel) = self.cancel_channel.take() {
 400            _ = channel.send(());
 401        }
 402
 403        let (sender, mut receiver) = oneshot::channel::<()>();
 404        self.cancel_channel = Some(sender);
 405
 406        let previous_task = self.task.take();
 407        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 408            let mut timer = cx.background_executor().timer(delay).fuse();
 409            if let Some(previous_task) = previous_task {
 410                previous_task.await;
 411            }
 412
 413            futures::select_biased! {
 414                _ = receiver => return,
 415                    _ = timer => {}
 416            }
 417
 418            if let Some(result) = workspace
 419                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 420                .log_err()
 421            {
 422                result.await.log_err();
 423            }
 424        }));
 425    }
 426}
 427
 428pub enum Event {
 429    PaneAdded(View<Pane>),
 430    ContactRequestedJoin(u64),
 431    WorkspaceCreated(WeakView<Workspace>),
 432}
 433
 434pub struct Workspace {
 435    window_self: WindowHandle<Self>,
 436    weak_self: WeakView<Self>,
 437    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 438    zoomed: Option<AnyWeakView>,
 439    zoomed_position: Option<DockPosition>,
 440    center: PaneGroup,
 441    left_dock: View<Dock>,
 442    bottom_dock: View<Dock>,
 443    right_dock: View<Dock>,
 444    panes: Vec<View<Pane>>,
 445    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 446    active_pane: View<Pane>,
 447    last_active_center_pane: Option<WeakView<Pane>>,
 448    last_active_view_id: Option<proto::ViewId>,
 449    status_bar: View<StatusBar>,
 450    modal_layer: View<ModalLayer>,
 451    titlebar_item: Option<AnyView>,
 452    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 453    project: Model<Project>,
 454    follower_states: HashMap<View<Pane>, FollowerState>,
 455    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 456    window_edited: bool,
 457    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 458    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 459    database_id: WorkspaceId,
 460    app_state: Arc<AppState>,
 461    subscriptions: Vec<Subscription>,
 462    _apply_leader_updates: Task<Result<()>>,
 463    _observe_current_user: Task<Result<()>>,
 464    _schedule_serialize: Option<Task<()>>,
 465    pane_history_timestamp: Arc<AtomicUsize>,
 466    bounds: Bounds<Pixels>,
 467}
 468
 469impl EventEmitter<Event> for Workspace {}
 470
 471#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 472pub struct ViewId {
 473    pub creator: PeerId,
 474    pub id: u64,
 475}
 476
 477#[derive(Default)]
 478struct FollowerState {
 479    leader_id: PeerId,
 480    active_view_id: Option<ViewId>,
 481    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 482}
 483
 484impl Workspace {
 485    pub fn new(
 486        workspace_id: WorkspaceId,
 487        project: Model<Project>,
 488        app_state: Arc<AppState>,
 489        cx: &mut ViewContext<Self>,
 490    ) -> Self {
 491        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 492        cx.subscribe(&project, move |this, _, event, cx| {
 493            match event {
 494                project::Event::RemoteIdChanged(_) => {
 495                    this.update_window_title(cx);
 496                }
 497
 498                project::Event::CollaboratorLeft(peer_id) => {
 499                    this.collaborator_left(*peer_id, cx);
 500                }
 501
 502                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 503                    this.update_window_title(cx);
 504                    this.serialize_workspace(cx);
 505                }
 506
 507                project::Event::DisconnectedFromHost => {
 508                    this.update_window_edited(cx);
 509                    cx.blur();
 510                }
 511
 512                project::Event::Closed => {
 513                    cx.remove_window();
 514                }
 515
 516                project::Event::DeletedEntry(entry_id) => {
 517                    for pane in this.panes.iter() {
 518                        pane.update(cx, |pane, cx| {
 519                            pane.handle_deleted_project_item(*entry_id, cx)
 520                        });
 521                    }
 522                }
 523
 524                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 525                    cx.build_view(|_| MessageNotification::new(message.clone()))
 526                }),
 527
 528                _ => {}
 529            }
 530            cx.notify()
 531        })
 532        .detach();
 533        cx.on_blur_window(|this, cx| {
 534            let focus_handle = this.focus_handle(cx);
 535            cx.focus(&focus_handle);
 536        })
 537        .detach();
 538
 539        let weak_handle = cx.view().downgrade();
 540        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 541
 542        let center_pane = cx.build_view(|cx| {
 543            Pane::new(
 544                weak_handle.clone(),
 545                project.clone(),
 546                pane_history_timestamp.clone(),
 547                cx,
 548            )
 549        });
 550        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 551
 552        cx.focus_view(&center_pane);
 553        cx.emit(Event::PaneAdded(center_pane.clone()));
 554
 555        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 556        app_state.workspace_store.update(cx, |store, _| {
 557            store.workspaces.insert(window_handle);
 558        });
 559
 560        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 561        let mut connection_status = app_state.client.status();
 562        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 563            current_user.next().await;
 564            connection_status.next().await;
 565            let mut stream =
 566                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 567
 568            while stream.recv().await.is_some() {
 569                this.update(&mut cx, |_, cx| cx.notify())?;
 570            }
 571            anyhow::Ok(())
 572        });
 573
 574        // All leader updates are enqueued and then processed in a single task, so
 575        // that each asynchronous operation can be run in order.
 576        let (leader_updates_tx, mut leader_updates_rx) =
 577            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 578        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 579            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 580                Self::process_leader_update(&this, leader_id, update, &mut cx)
 581                    .await
 582                    .log_err();
 583            }
 584
 585            Ok(())
 586        });
 587
 588        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 589
 590        let left_dock = cx.build_view(|cx| Dock::new(DockPosition::Left, cx));
 591        let bottom_dock = cx.build_view(|cx| Dock::new(DockPosition::Bottom, cx));
 592        let right_dock = cx.build_view(|cx| Dock::new(DockPosition::Right, cx));
 593        let left_dock_buttons =
 594            cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 595        let bottom_dock_buttons =
 596            cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 597        let right_dock_buttons =
 598            cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 599        let status_bar = cx.build_view(|cx| {
 600            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 601            status_bar.add_left_item(left_dock_buttons, cx);
 602            status_bar.add_right_item(right_dock_buttons, cx);
 603            status_bar.add_right_item(bottom_dock_buttons, cx);
 604            status_bar
 605        });
 606
 607        let workspace_handle = cx.view().downgrade();
 608        let modal_layer = cx.build_view(|cx| ModalLayer::new());
 609
 610        // todo!()
 611        // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 612        //     drag_and_drop.register_container(weak_handle.clone());
 613        // });
 614
 615        let mut active_call = None;
 616        if cx.has_global::<Model<ActiveCall>>() {
 617            let call = cx.global::<Model<ActiveCall>>().clone();
 618            let mut subscriptions = Vec::new();
 619            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 620            active_call = Some((call, subscriptions));
 621        }
 622
 623        let subscriptions = vec![
 624            cx.observe_window_activation(Self::on_window_activation_changed),
 625            cx.observe_window_bounds(move |_, cx| {
 626                if let Some(display) = cx.display() {
 627                    // Transform fixed bounds to be stored in terms of the containing display
 628                    let mut bounds = cx.window_bounds();
 629                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
 630                        let display_bounds = display.bounds();
 631                        window_bounds.origin.x -= display_bounds.origin.x;
 632                        window_bounds.origin.y -= display_bounds.origin.y;
 633                    }
 634
 635                    if let Some(display_uuid) = display.uuid().log_err() {
 636                        cx.background_executor()
 637                            .spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid))
 638                            .detach_and_log_err(cx);
 639                    }
 640                }
 641                cx.notify();
 642            }),
 643            cx.observe(&left_dock, |this, _, cx| {
 644                this.serialize_workspace(cx);
 645                cx.notify();
 646            }),
 647            cx.observe(&bottom_dock, |this, _, cx| {
 648                this.serialize_workspace(cx);
 649                cx.notify();
 650            }),
 651            cx.observe(&right_dock, |this, _, cx| {
 652                this.serialize_workspace(cx);
 653                cx.notify();
 654            }),
 655            cx.on_release(|this, cx| {
 656                this.app_state.workspace_store.update(cx, |store, _| {
 657                    store.workspaces.remove(&this.window_self);
 658                })
 659            }),
 660        ];
 661
 662        cx.defer(|this, cx| {
 663            this.update_window_title(cx);
 664            // todo! @nate - these are useful for testing notifications
 665            // this.show_error(
 666            //     &anyhow::anyhow!("what happens if this message is very very very very very long"),
 667            //     cx,
 668            // );
 669
 670            // this.show_notification(1, cx, |cx| {
 671            //     cx.build_view(|_cx| {
 672            //         simple_message_notification::MessageNotification::new(format!("Error:"))
 673            //             .with_click_message("click here because!")
 674            //     })
 675            // });
 676        });
 677        Workspace {
 678            window_self: window_handle,
 679            weak_self: weak_handle.clone(),
 680            zoomed: None,
 681            zoomed_position: None,
 682            center: PaneGroup::new(center_pane.clone()),
 683            panes: vec![center_pane.clone()],
 684            panes_by_item: Default::default(),
 685            active_pane: center_pane.clone(),
 686            last_active_center_pane: Some(center_pane.downgrade()),
 687            last_active_view_id: None,
 688            status_bar,
 689            modal_layer,
 690            titlebar_item: None,
 691            notifications: Default::default(),
 692            left_dock,
 693            bottom_dock,
 694            right_dock,
 695            project: project.clone(),
 696            follower_states: Default::default(),
 697            last_leaders_by_pane: Default::default(),
 698            window_edited: false,
 699            active_call,
 700            database_id: workspace_id,
 701            app_state,
 702            _observe_current_user,
 703            _apply_leader_updates,
 704            _schedule_serialize: None,
 705            leader_updates_tx,
 706            subscriptions,
 707            pane_history_timestamp,
 708            workspace_actions: Default::default(),
 709            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 710            bounds: 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#[derive(Render)]
3584struct DraggedDock(DockPosition);
3585
3586impl Render for Workspace {
3587    type Element = Div;
3588
3589    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3590        let mut context = KeyContext::default();
3591        context.add("Workspace");
3592
3593        let (ui_font, ui_font_size) = {
3594            let theme_settings = ThemeSettings::get_global(cx);
3595            (
3596                theme_settings.ui_font.family.clone(),
3597                theme_settings.ui_font_size.clone(),
3598            )
3599        };
3600
3601        cx.set_rem_size(ui_font_size);
3602
3603        self.actions(div(), cx)
3604            .key_context(context)
3605            .relative()
3606            .size_full()
3607            .flex()
3608            .flex_col()
3609            .font(ui_font)
3610            .gap_0()
3611            .justify_start()
3612            .items_start()
3613            .text_color(cx.theme().colors().text)
3614            .bg(cx.theme().colors().background)
3615            .border()
3616            .border_color(cx.theme().colors().border)
3617            .children(self.titlebar_item.clone())
3618            .child(
3619                div()
3620                    .id("workspace")
3621                    .relative()
3622                    .flex_1()
3623                    .w_full()
3624                    .flex()
3625                    .flex_col()
3626                    .overflow_hidden()
3627                    .border_t()
3628                    .border_b()
3629                    .border_color(cx.theme().colors().border)
3630                    .child(
3631                        canvas(cx.listener(|workspace, bounds, cx| {
3632                            workspace.bounds = *bounds;
3633                        }))
3634                        .absolute()
3635                        .size_full(),
3636                    )
3637                    .on_drag_move(
3638                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3639                            match e.drag.read(cx).0 {
3640                                DockPosition::Left => {
3641                                    let size = workspace.bounds.left() + e.event.position.x;
3642                                    workspace.left_dock.update(cx, |left_dock, cx| {
3643                                        left_dock.resize_active_panel(Some(size.0), cx);
3644                                    });
3645                                }
3646                                DockPosition::Right => {
3647                                    let size = workspace.bounds.right() - e.event.position.x;
3648                                    workspace.right_dock.update(cx, |right_dock, cx| {
3649                                        right_dock.resize_active_panel(Some(size.0), cx);
3650                                    });
3651                                }
3652                                DockPosition::Bottom => {
3653                                    let size = workspace.bounds.bottom() - e.event.position.y;
3654                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3655                                        bottom_dock.resize_active_panel(Some(size.0), cx);
3656                                    });
3657                                }
3658                            }
3659                        }),
3660                    )
3661                    .child(self.modal_layer.clone())
3662                    .child(
3663                        div()
3664                            .flex()
3665                            .flex_row()
3666                            .h_full()
3667                            // Left Dock
3668                            .child(
3669                                div()
3670                                    .flex()
3671                                    .flex_none()
3672                                    .overflow_hidden()
3673                                    .child(self.left_dock.clone()),
3674                            )
3675                            // Panes
3676                            .child(
3677                                div()
3678                                    .flex()
3679                                    .flex_col()
3680                                    .flex_1()
3681                                    .overflow_hidden()
3682                                    .child(self.center.render(
3683                                        &self.project,
3684                                        &self.follower_states,
3685                                        self.active_call(),
3686                                        &self.active_pane,
3687                                        self.zoomed.as_ref(),
3688                                        &self.app_state,
3689                                        cx,
3690                                    ))
3691                                    .child(self.bottom_dock.clone()),
3692                            )
3693                            // Right Dock
3694                            .child(
3695                                div()
3696                                    .flex()
3697                                    .flex_none()
3698                                    .overflow_hidden()
3699                                    .child(self.right_dock.clone()),
3700                            ),
3701                    )
3702                    .children(self.render_notifications(cx)),
3703            )
3704            .child(self.status_bar.clone())
3705    }
3706}
3707
3708// impl View for Workspace {
3709
3710//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3711//         let theme = theme::current(cx).clone();
3712//         Stack::new()
3713//             .with_child(
3714//                 Flex::column()
3715//                     .with_child(self.render_titlebar(&theme, cx))
3716//                     .with_child(
3717//                         Stack::new()
3718//                             .with_child({
3719//                                 let project = self.project.clone();
3720//                                 Flex::row()
3721//                                     .with_children(self.render_dock(DockPosition::Left, cx))
3722//                                     .with_child(
3723//                                         Flex::column()
3724//                                             .with_child(
3725//                                                 FlexItem::new(
3726//                                                     self.center.render(
3727//                                                         &project,
3728//                                                         &theme,
3729//                                                         &self.follower_states,
3730//                                                         self.active_call(),
3731//                                                         self.active_pane(),
3732//                                                         self.zoomed
3733//                                                             .as_ref()
3734//                                                             .and_then(|zoomed| zoomed.upgrade(cx))
3735//                                                             .as_ref(),
3736//                                                         &self.app_state,
3737//                                                         cx,
3738//                                                     ),
3739//                                                 )
3740//                                                 .flex(1., true),
3741//                                             )
3742//                                             .with_children(
3743//                                                 self.render_dock(DockPosition::Bottom, cx),
3744//                                             )
3745//                                             .flex(1., true),
3746//                                     )
3747//                                     .with_children(self.render_dock(DockPosition::Right, cx))
3748//                             })
3749//                             .with_child(Overlay::new(
3750//                                 Stack::new()
3751//                                     .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3752//                                         enum ZoomBackground {}
3753//                                         let zoomed = zoomed.upgrade(cx)?;
3754
3755//                                         let mut foreground_style =
3756//                                             theme.workspace.zoomed_pane_foreground;
3757//                                         if let Some(zoomed_dock_position) = self.zoomed_position {
3758//                                             foreground_style =
3759//                                                 theme.workspace.zoomed_panel_foreground;
3760//                                             let margin = foreground_style.margin.top;
3761//                                             let border = foreground_style.border.top;
3762
3763//                                             // Only include a margin and border on the opposite side.
3764//                                             foreground_style.margin.top = 0.;
3765//                                             foreground_style.margin.left = 0.;
3766//                                             foreground_style.margin.bottom = 0.;
3767//                                             foreground_style.margin.right = 0.;
3768//                                             foreground_style.border.top = false;
3769//                                             foreground_style.border.left = false;
3770//                                             foreground_style.border.bottom = false;
3771//                                             foreground_style.border.right = false;
3772//                                             match zoomed_dock_position {
3773//                                                 DockPosition::Left => {
3774//                                                     foreground_style.margin.right = margin;
3775//                                                     foreground_style.border.right = border;
3776//                                                 }
3777//                                                 DockPosition::Right => {
3778//                                                     foreground_style.margin.left = margin;
3779//                                                     foreground_style.border.left = border;
3780//                                                 }
3781//                                                 DockPosition::Bottom => {
3782//                                                     foreground_style.margin.top = margin;
3783//                                                     foreground_style.border.top = border;
3784//                                                 }
3785//                                             }
3786//                                         }
3787
3788//                                         Some(
3789//                                             ChildView::new(&zoomed, cx)
3790//                                                 .contained()
3791//                                                 .with_style(foreground_style)
3792//                                                 .aligned()
3793//                                                 .contained()
3794//                                                 .with_style(theme.workspace.zoomed_background)
3795//                                                 .mouse::<ZoomBackground>(0)
3796//                                                 .capture_all()
3797//                                                 .on_down(
3798//                                                     MouseButton::Left,
3799//                                                     |_, this: &mut Self, cx| {
3800//                                                         this.zoom_out(cx);
3801//                                                     },
3802//                                                 ),
3803//                                         )
3804//                                     }))
3805//                                     .with_children(self.modal.as_ref().map(|modal| {
3806//                                         // Prevent clicks within the modal from falling
3807//                                         // through to the rest of the workspace.
3808//                                         enum ModalBackground {}
3809//                                         MouseEventHandler::new::<ModalBackground, _>(
3810//                                             0,
3811//                                             cx,
3812//                                             |_, cx| ChildView::new(modal.view.as_any(), cx),
3813//                                         )
3814//                                         .on_click(MouseButton::Left, |_, _, _| {})
3815//                                         .contained()
3816//                                         .with_style(theme.workspace.modal)
3817//                                         .aligned()
3818//                                         .top()
3819//                                     }))
3820//                                     .with_children(self.render_notifications(&theme.workspace, cx)),
3821//                             ))
3822//                             .provide_resize_bounds::<WorkspaceBounds>()
3823//                             .flex(1.0, true),
3824//                     )
3825//                     .with_child(ChildView::new(&self.status_bar, cx))
3826//                     .contained()
3827//                     .with_background_color(theme.workspace.background),
3828//             )
3829//             .with_children(DragAndDrop::render(cx))
3830//             .with_children(self.render_disconnected_overlay(cx))
3831//             .into_any_named("workspace")
3832//     }
3833
3834//     fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3835//         DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3836//     }
3837// }
3838
3839impl WorkspaceStore {
3840    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3841        Self {
3842            workspaces: Default::default(),
3843            followers: Default::default(),
3844            _subscriptions: vec![
3845                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3846                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3847                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3848            ],
3849            client,
3850        }
3851    }
3852
3853    pub fn update_followers(
3854        &self,
3855        project_id: Option<u64>,
3856        update: proto::update_followers::Variant,
3857        cx: &AppContext,
3858    ) -> Option<()> {
3859        if !cx.has_global::<Model<ActiveCall>>() {
3860            return None;
3861        }
3862
3863        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3864        let follower_ids: Vec<_> = self
3865            .followers
3866            .iter()
3867            .filter_map(|follower| {
3868                if follower.project_id == project_id || project_id.is_none() {
3869                    Some(follower.peer_id.into())
3870                } else {
3871                    None
3872                }
3873            })
3874            .collect();
3875        if follower_ids.is_empty() {
3876            return None;
3877        }
3878        self.client
3879            .send(proto::UpdateFollowers {
3880                room_id,
3881                project_id,
3882                follower_ids,
3883                variant: Some(update),
3884            })
3885            .log_err()
3886    }
3887
3888    pub async fn handle_follow(
3889        this: Model<Self>,
3890        envelope: TypedEnvelope<proto::Follow>,
3891        _: Arc<Client>,
3892        mut cx: AsyncAppContext,
3893    ) -> Result<proto::FollowResponse> {
3894        this.update(&mut cx, |this, cx| {
3895            let follower = Follower {
3896                project_id: envelope.payload.project_id,
3897                peer_id: envelope.original_sender_id()?,
3898            };
3899            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3900
3901            let mut response = proto::FollowResponse::default();
3902            for workspace in &this.workspaces {
3903                workspace
3904                    .update(cx, |workspace, cx| {
3905                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3906                        if response.views.is_empty() {
3907                            response.views = handler_response.views;
3908                        } else {
3909                            response.views.extend_from_slice(&handler_response.views);
3910                        }
3911
3912                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3913                            if response.active_view_id.is_none()
3914                                || Some(workspace.project.downgrade()) == active_project
3915                            {
3916                                response.active_view_id = Some(active_view_id);
3917                            }
3918                        }
3919                    })
3920                    .ok();
3921            }
3922
3923            if let Err(ix) = this.followers.binary_search(&follower) {
3924                this.followers.insert(ix, follower);
3925            }
3926
3927            Ok(response)
3928        })?
3929    }
3930
3931    async fn handle_unfollow(
3932        model: Model<Self>,
3933        envelope: TypedEnvelope<proto::Unfollow>,
3934        _: Arc<Client>,
3935        mut cx: AsyncAppContext,
3936    ) -> Result<()> {
3937        model.update(&mut cx, |this, _| {
3938            let follower = Follower {
3939                project_id: envelope.payload.project_id,
3940                peer_id: envelope.original_sender_id()?,
3941            };
3942            if let Ok(ix) = this.followers.binary_search(&follower) {
3943                this.followers.remove(ix);
3944            }
3945            Ok(())
3946        })?
3947    }
3948
3949    async fn handle_update_followers(
3950        this: Model<Self>,
3951        envelope: TypedEnvelope<proto::UpdateFollowers>,
3952        _: Arc<Client>,
3953        mut cx: AsyncAppContext,
3954    ) -> Result<()> {
3955        let leader_id = envelope.original_sender_id()?;
3956        let update = envelope.payload;
3957
3958        this.update(&mut cx, |this, cx| {
3959            for workspace in &this.workspaces {
3960                workspace.update(cx, |workspace, cx| {
3961                    let project_id = workspace.project.read(cx).remote_id();
3962                    if update.project_id != project_id && update.project_id.is_some() {
3963                        return;
3964                    }
3965                    workspace.handle_update_followers(leader_id, update.clone(), cx);
3966                })?;
3967            }
3968            Ok(())
3969        })?
3970    }
3971}
3972
3973impl ViewId {
3974    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3975        Ok(Self {
3976            creator: message
3977                .creator
3978                .ok_or_else(|| anyhow!("creator is missing"))?,
3979            id: message.id,
3980        })
3981    }
3982
3983    pub(crate) fn to_proto(&self) -> proto::ViewId {
3984        proto::ViewId {
3985            creator: Some(self.creator),
3986            id: self.id,
3987        }
3988    }
3989}
3990
3991pub trait WorkspaceHandle {
3992    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3993}
3994
3995impl WorkspaceHandle for View<Workspace> {
3996    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3997        self.read(cx)
3998            .worktrees(cx)
3999            .flat_map(|worktree| {
4000                let worktree_id = worktree.read(cx).id();
4001                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4002                    worktree_id,
4003                    path: f.path.clone(),
4004                })
4005            })
4006            .collect::<Vec<_>>()
4007    }
4008}
4009
4010impl std::fmt::Debug for OpenPaths {
4011    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4012        f.debug_struct("OpenPaths")
4013            .field("paths", &self.paths)
4014            .finish()
4015    }
4016}
4017
4018pub fn activate_workspace_for_project(
4019    cx: &mut AppContext,
4020    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4021) -> Option<WindowHandle<Workspace>> {
4022    for window in cx.windows() {
4023        let Some(workspace) = window.downcast::<Workspace>() else {
4024            continue;
4025        };
4026
4027        let predicate = workspace
4028            .update(cx, |workspace, cx| {
4029                let project = workspace.project.read(cx);
4030                if predicate(project, cx) {
4031                    cx.activate_window();
4032                    true
4033                } else {
4034                    false
4035                }
4036            })
4037            .log_err()
4038            .unwrap_or(false);
4039
4040        if predicate {
4041            return Some(workspace);
4042        }
4043    }
4044
4045    None
4046}
4047
4048pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4049    DB.last_workspace().await.log_err().flatten()
4050}
4051
4052async fn join_channel_internal(
4053    channel_id: u64,
4054    app_state: &Arc<AppState>,
4055    requesting_window: Option<WindowHandle<Workspace>>,
4056    active_call: &Model<ActiveCall>,
4057    cx: &mut AsyncAppContext,
4058) -> Result<bool> {
4059    let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4060        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4061            return (false, None);
4062        };
4063
4064        let already_in_channel = room.channel_id() == Some(channel_id);
4065        let should_prompt = room.is_sharing_project()
4066            && room.remote_participants().len() > 0
4067            && !already_in_channel;
4068        let open_room = if already_in_channel {
4069            active_call.room().cloned()
4070        } else {
4071            None
4072        };
4073        (should_prompt, open_room)
4074    })?;
4075
4076    if let Some(room) = open_room {
4077        let task = room.update(cx, |room, cx| {
4078            if let Some((project, host)) = room.most_active_project(cx) {
4079                return Some(join_remote_project(project, host, app_state.clone(), cx));
4080            }
4081
4082            None
4083        })?;
4084        if let Some(task) = task {
4085            task.await?;
4086        }
4087        return anyhow::Ok(true);
4088    }
4089
4090    if should_prompt {
4091        if let Some(workspace) = requesting_window {
4092            let answer  = workspace.update(cx, |_, cx| {
4093                cx.prompt(
4094                    PromptLevel::Warning,
4095                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4096                    &["Yes, Join Channel", "Cancel"],
4097                )
4098            })?.await;
4099
4100            if answer == Ok(1) {
4101                return Ok(false);
4102            }
4103        } else {
4104            return Ok(false); // unreachable!() hopefully
4105        }
4106    }
4107
4108    let client = cx.update(|cx| active_call.read(cx).client())?;
4109
4110    let mut client_status = client.status();
4111
4112    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4113    'outer: loop {
4114        let Some(status) = client_status.recv().await else {
4115            return Err(anyhow!("error connecting"));
4116        };
4117
4118        match status {
4119            Status::Connecting
4120            | Status::Authenticating
4121            | Status::Reconnecting
4122            | Status::Reauthenticating => continue,
4123            Status::Connected { .. } => break 'outer,
4124            Status::SignedOut => return Err(anyhow!("not signed in")),
4125            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4126            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4127                return Err(anyhow!("zed is offline"))
4128            }
4129        }
4130    }
4131
4132    let room = active_call
4133        .update(cx, |active_call, cx| {
4134            active_call.join_channel(channel_id, cx)
4135        })?
4136        .await?;
4137
4138    let Some(room) = room else {
4139        return anyhow::Ok(true);
4140    };
4141
4142    room.update(cx, |room, _| room.room_update_completed())?
4143        .await;
4144
4145    let task = room.update(cx, |room, cx| {
4146        if let Some((project, host)) = room.most_active_project(cx) {
4147            return Some(join_remote_project(project, host, app_state.clone(), cx));
4148        }
4149
4150        None
4151    })?;
4152    if let Some(task) = task {
4153        task.await?;
4154        return anyhow::Ok(true);
4155    }
4156    anyhow::Ok(false)
4157}
4158
4159pub fn join_channel(
4160    channel_id: u64,
4161    app_state: Arc<AppState>,
4162    requesting_window: Option<WindowHandle<Workspace>>,
4163    cx: &mut AppContext,
4164) -> Task<Result<()>> {
4165    let active_call = ActiveCall::global(cx);
4166    cx.spawn(|mut cx| async move {
4167        let result = join_channel_internal(
4168            channel_id,
4169            &app_state,
4170            requesting_window,
4171            &active_call,
4172            &mut cx,
4173        )
4174        .await;
4175
4176        // join channel succeeded, and opened a window
4177        if matches!(result, Ok(true)) {
4178            return anyhow::Ok(());
4179        }
4180
4181        if requesting_window.is_some() {
4182            return anyhow::Ok(());
4183        }
4184
4185        // find an existing workspace to focus and show call controls
4186        let mut active_window = activate_any_workspace_window(&mut cx);
4187        if active_window.is_none() {
4188            // no open workspaces, make one to show the error in (blergh)
4189            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
4190                .await?;
4191        }
4192
4193        active_window = activate_any_workspace_window(&mut cx);
4194        let Some(active_window) = active_window else {
4195            return anyhow::Ok(());
4196        };
4197
4198        if let Err(err) = result {
4199            active_window
4200                .update(&mut cx, |_, cx| {
4201                    cx.prompt(
4202                        PromptLevel::Critical,
4203                        &format!("Failed to join channel: {}", err),
4204                        &["Ok"],
4205                    )
4206                })?
4207                .await
4208                .ok();
4209        }
4210
4211        // return ok, we showed the error to the user.
4212        return anyhow::Ok(());
4213    })
4214}
4215
4216pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4217    cx.update(|cx| {
4218        for window in cx.windows() {
4219            let is_workspace = window.downcast::<Workspace>().is_some();
4220            if is_workspace {
4221                window.update(cx, |_, cx| cx.activate_window()).ok();
4222                return Some(window);
4223            }
4224        }
4225        None
4226    })
4227    .ok()
4228    .flatten()
4229}
4230
4231#[allow(clippy::type_complexity)]
4232pub fn open_paths(
4233    abs_paths: &[PathBuf],
4234    app_state: &Arc<AppState>,
4235    requesting_window: Option<WindowHandle<Workspace>>,
4236    cx: &mut AppContext,
4237) -> Task<
4238    anyhow::Result<(
4239        WindowHandle<Workspace>,
4240        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4241    )>,
4242> {
4243    let app_state = app_state.clone();
4244    let abs_paths = abs_paths.to_vec();
4245    // Open paths in existing workspace if possible
4246    let existing = activate_workspace_for_project(cx, {
4247        let abs_paths = abs_paths.clone();
4248        move |project, cx| project.contains_paths(&abs_paths, cx)
4249    });
4250    cx.spawn(move |mut cx| async move {
4251        if let Some(existing) = existing {
4252            Ok((
4253                existing.clone(),
4254                existing
4255                    .update(&mut cx, |workspace, cx| {
4256                        workspace.open_paths(abs_paths, true, cx)
4257                    })?
4258                    .await,
4259            ))
4260        } else {
4261            cx.update(move |cx| {
4262                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4263            })?
4264            .await
4265        }
4266    })
4267}
4268
4269pub fn open_new(
4270    app_state: &Arc<AppState>,
4271    cx: &mut AppContext,
4272    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4273) -> Task<()> {
4274    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4275    cx.spawn(|mut cx| async move {
4276        if let Some((workspace, opened_paths)) = task.await.log_err() {
4277            workspace
4278                .update(&mut cx, |workspace, cx| {
4279                    if opened_paths.is_empty() {
4280                        init(workspace, cx)
4281                    }
4282                })
4283                .log_err();
4284        }
4285    })
4286}
4287
4288pub fn create_and_open_local_file(
4289    path: &'static Path,
4290    cx: &mut ViewContext<Workspace>,
4291    default_content: impl 'static + Send + FnOnce() -> Rope,
4292) -> Task<Result<Box<dyn ItemHandle>>> {
4293    cx.spawn(|workspace, mut cx| async move {
4294        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4295        if !fs.is_file(path).await {
4296            fs.create_file(path, Default::default()).await?;
4297            fs.save(path, &default_content(), Default::default())
4298                .await?;
4299        }
4300
4301        let mut items = workspace
4302            .update(&mut cx, |workspace, cx| {
4303                workspace.with_local_workspace(cx, |workspace, cx| {
4304                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4305                })
4306            })?
4307            .await?
4308            .await;
4309
4310        let item = items.pop().flatten();
4311        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4312    })
4313}
4314
4315pub fn join_remote_project(
4316    project_id: u64,
4317    follow_user_id: u64,
4318    app_state: Arc<AppState>,
4319    cx: &mut AppContext,
4320) -> Task<Result<()>> {
4321    let windows = cx.windows();
4322    cx.spawn(|mut cx| async move {
4323        let existing_workspace = windows.into_iter().find_map(|window| {
4324            window.downcast::<Workspace>().and_then(|window| {
4325                window
4326                    .update(&mut cx, |workspace, cx| {
4327                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4328                            Some(window)
4329                        } else {
4330                            None
4331                        }
4332                    })
4333                    .unwrap_or(None)
4334            })
4335        });
4336
4337        let workspace = if let Some(existing_workspace) = existing_workspace {
4338            existing_workspace
4339        } else {
4340            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4341            let room = active_call
4342                .read_with(&cx, |call, _| call.room().cloned())?
4343                .ok_or_else(|| anyhow!("not in a call"))?;
4344            let project = room
4345                .update(&mut cx, |room, cx| {
4346                    room.join_project(
4347                        project_id,
4348                        app_state.languages.clone(),
4349                        app_state.fs.clone(),
4350                        cx,
4351                    )
4352                })?
4353                .await?;
4354
4355            let window_bounds_override = window_bounds_env_override(&cx);
4356            cx.update(|cx| {
4357                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4358                cx.open_window(options, |cx| {
4359                    cx.build_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4360                })
4361            })?
4362        };
4363
4364        workspace.update(&mut cx, |workspace, cx| {
4365            cx.activate(true);
4366            cx.activate_window();
4367
4368            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4369                let follow_peer_id = room
4370                    .read(cx)
4371                    .remote_participants()
4372                    .iter()
4373                    .find(|(_, participant)| participant.user.id == follow_user_id)
4374                    .map(|(_, p)| p.peer_id)
4375                    .or_else(|| {
4376                        // If we couldn't follow the given user, follow the host instead.
4377                        let collaborator = workspace
4378                            .project()
4379                            .read(cx)
4380                            .collaborators()
4381                            .values()
4382                            .find(|collaborator| collaborator.replica_id == 0)?;
4383                        Some(collaborator.peer_id)
4384                    });
4385
4386                if let Some(follow_peer_id) = follow_peer_id {
4387                    workspace
4388                        .follow(follow_peer_id, cx)
4389                        .map(|follow| follow.detach_and_log_err(cx));
4390                }
4391            }
4392        })?;
4393
4394        anyhow::Ok(())
4395    })
4396}
4397
4398pub fn restart(_: &Restart, cx: &mut AppContext) {
4399    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4400    let mut workspace_windows = cx
4401        .windows()
4402        .into_iter()
4403        .filter_map(|window| window.downcast::<Workspace>())
4404        .collect::<Vec<_>>();
4405
4406    // If multiple windows have unsaved changes, and need a save prompt,
4407    // prompt in the active window before switching to a different window.
4408    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4409
4410    let mut prompt = None;
4411    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4412        prompt = window
4413            .update(cx, |_, cx| {
4414                cx.prompt(
4415                    PromptLevel::Info,
4416                    "Are you sure you want to restart?",
4417                    &["Restart", "Cancel"],
4418                )
4419            })
4420            .ok();
4421    }
4422
4423    cx.spawn(|mut cx| async move {
4424        if let Some(mut prompt) = prompt {
4425            let answer = prompt.await?;
4426            if answer != 0 {
4427                return Ok(());
4428            }
4429        }
4430
4431        // If the user cancels any save prompt, then keep the app open.
4432        for window in workspace_windows {
4433            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4434                workspace.prepare_to_close(true, cx)
4435            }) {
4436                if !should_close.await? {
4437                    return Ok(());
4438                }
4439            }
4440        }
4441
4442        cx.update(|cx| cx.restart())
4443    })
4444    .detach_and_log_err(cx);
4445}
4446
4447fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4448    let mut parts = value.split(',');
4449    let x: usize = parts.next()?.parse().ok()?;
4450    let y: usize = parts.next()?.parse().ok()?;
4451    Some(point((x as f64).into(), (y as f64).into()))
4452}
4453
4454fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4455    let mut parts = value.split(',');
4456    let width: usize = parts.next()?.parse().ok()?;
4457    let height: usize = parts.next()?.parse().ok()?;
4458    Some(size((width as f64).into(), (height as f64).into()))
4459}
4460
4461#[cfg(test)]
4462mod tests {
4463    use super::*;
4464    use crate::item::{
4465        test::{TestItem, TestProjectItem},
4466        ItemEvent,
4467    };
4468    use fs::FakeFs;
4469    use gpui::TestAppContext;
4470    use project::{Project, ProjectEntryId};
4471    use serde_json::json;
4472    use settings::SettingsStore;
4473    use std::{cell::RefCell, rc::Rc};
4474
4475    #[gpui::test]
4476    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4477        init_test(cx);
4478
4479        let fs = FakeFs::new(cx.executor());
4480        let project = Project::test(fs, [], cx).await;
4481        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4482
4483        // Adding an item with no ambiguity renders the tab without detail.
4484        let item1 = cx.build_view(|cx| {
4485            let mut item = TestItem::new(cx);
4486            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4487            item
4488        });
4489        workspace.update(cx, |workspace, cx| {
4490            workspace.add_item(Box::new(item1.clone()), cx);
4491        });
4492        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4493
4494        // Adding an item that creates ambiguity increases the level of detail on
4495        // both tabs.
4496        let item2 = cx.build_view(|cx| {
4497            let mut item = TestItem::new(cx);
4498            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4499            item
4500        });
4501        workspace.update(cx, |workspace, cx| {
4502            workspace.add_item(Box::new(item2.clone()), cx);
4503        });
4504        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4505        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4506
4507        // Adding an item that creates ambiguity increases the level of detail only
4508        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4509        // we stop at the highest detail available.
4510        let item3 = cx.build_view(|cx| {
4511            let mut item = TestItem::new(cx);
4512            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4513            item
4514        });
4515        workspace.update(cx, |workspace, cx| {
4516            workspace.add_item(Box::new(item3.clone()), cx);
4517        });
4518        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4519        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4520        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4521    }
4522
4523    #[gpui::test]
4524    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4525        init_test(cx);
4526
4527        let fs = FakeFs::new(cx.executor());
4528        fs.insert_tree(
4529            "/root1",
4530            json!({
4531                "one.txt": "",
4532                "two.txt": "",
4533            }),
4534        )
4535        .await;
4536        fs.insert_tree(
4537            "/root2",
4538            json!({
4539                "three.txt": "",
4540            }),
4541        )
4542        .await;
4543
4544        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4545        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4546        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4547        let worktree_id = project.read_with(cx, |project, cx| {
4548            project.worktrees().next().unwrap().read(cx).id()
4549        });
4550
4551        let item1 = cx.build_view(|cx| {
4552            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4553        });
4554        let item2 = cx.build_view(|cx| {
4555            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4556        });
4557
4558        // Add an item to an empty pane
4559        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4560        project.read_with(cx, |project, cx| {
4561            assert_eq!(
4562                project.active_entry(),
4563                project
4564                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4565                    .map(|e| e.id)
4566            );
4567        });
4568        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4569
4570        // Add a second item to a non-empty pane
4571        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4572        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
4573        project.read_with(cx, |project, cx| {
4574            assert_eq!(
4575                project.active_entry(),
4576                project
4577                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4578                    .map(|e| e.id)
4579            );
4580        });
4581
4582        // Close the active item
4583        pane.update(cx, |pane, cx| {
4584            pane.close_active_item(&Default::default(), cx).unwrap()
4585        })
4586        .await
4587        .unwrap();
4588        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4589        project.read_with(cx, |project, cx| {
4590            assert_eq!(
4591                project.active_entry(),
4592                project
4593                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4594                    .map(|e| e.id)
4595            );
4596        });
4597
4598        // Add a project folder
4599        project
4600            .update(cx, |project, cx| {
4601                project.find_or_create_local_worktree("/root2", true, cx)
4602            })
4603            .await
4604            .unwrap();
4605        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
4606
4607        // Remove a project folder
4608        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4609        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
4610    }
4611
4612    #[gpui::test]
4613    async fn test_close_window(cx: &mut TestAppContext) {
4614        init_test(cx);
4615
4616        let fs = FakeFs::new(cx.executor());
4617        fs.insert_tree("/root", json!({ "one": "" })).await;
4618
4619        let project = Project::test(fs, ["root".as_ref()], cx).await;
4620        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4621
4622        // When there are no dirty items, there's nothing to do.
4623        let item1 = cx.build_view(|cx| TestItem::new(cx));
4624        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4625        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4626        assert!(task.await.unwrap());
4627
4628        // When there are dirty untitled items, prompt to save each one. If the user
4629        // cancels any prompt, then abort.
4630        let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
4631        let item3 = cx.build_view(|cx| {
4632            TestItem::new(cx)
4633                .with_dirty(true)
4634                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4635        });
4636        workspace.update(cx, |w, cx| {
4637            w.add_item(Box::new(item2.clone()), cx);
4638            w.add_item(Box::new(item3.clone()), cx);
4639        });
4640        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4641        cx.executor().run_until_parked();
4642        cx.simulate_prompt_answer(2); // cancel save all
4643        cx.executor().run_until_parked();
4644        cx.simulate_prompt_answer(2); // cancel save all
4645        cx.executor().run_until_parked();
4646        assert!(!cx.has_pending_prompt());
4647        assert!(!task.await.unwrap());
4648    }
4649
4650    #[gpui::test]
4651    async fn test_close_pane_items(cx: &mut TestAppContext) {
4652        init_test(cx);
4653
4654        let fs = FakeFs::new(cx.executor());
4655
4656        let project = Project::test(fs, None, cx).await;
4657        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4658
4659        let item1 = cx.build_view(|cx| {
4660            TestItem::new(cx)
4661                .with_dirty(true)
4662                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4663        });
4664        let item2 = cx.build_view(|cx| {
4665            TestItem::new(cx)
4666                .with_dirty(true)
4667                .with_conflict(true)
4668                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4669        });
4670        let item3 = cx.build_view(|cx| {
4671            TestItem::new(cx)
4672                .with_dirty(true)
4673                .with_conflict(true)
4674                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4675        });
4676        let item4 = cx.build_view(|cx| {
4677            TestItem::new(cx)
4678                .with_dirty(true)
4679                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4680        });
4681        let pane = workspace.update(cx, |workspace, cx| {
4682            workspace.add_item(Box::new(item1.clone()), cx);
4683            workspace.add_item(Box::new(item2.clone()), cx);
4684            workspace.add_item(Box::new(item3.clone()), cx);
4685            workspace.add_item(Box::new(item4.clone()), cx);
4686            workspace.active_pane().clone()
4687        });
4688
4689        let close_items = pane.update(cx, |pane, cx| {
4690            pane.activate_item(1, true, true, cx);
4691            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4692            let item1_id = item1.item_id();
4693            let item3_id = item3.item_id();
4694            let item4_id = item4.item_id();
4695            pane.close_items(cx, SaveIntent::Close, move |id| {
4696                [item1_id, item3_id, item4_id].contains(&id)
4697            })
4698        });
4699        cx.executor().run_until_parked();
4700
4701        assert!(cx.has_pending_prompt());
4702        // Ignore "Save all" prompt
4703        cx.simulate_prompt_answer(2);
4704        cx.executor().run_until_parked();
4705        // There's a prompt to save item 1.
4706        pane.update(cx, |pane, _| {
4707            assert_eq!(pane.items_len(), 4);
4708            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4709        });
4710        // Confirm saving item 1.
4711        cx.simulate_prompt_answer(0);
4712        cx.executor().run_until_parked();
4713
4714        // Item 1 is saved. There's a prompt to save item 3.
4715        pane.update(cx, |pane, cx| {
4716            assert_eq!(item1.read(cx).save_count, 1);
4717            assert_eq!(item1.read(cx).save_as_count, 0);
4718            assert_eq!(item1.read(cx).reload_count, 0);
4719            assert_eq!(pane.items_len(), 3);
4720            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4721        });
4722        assert!(cx.has_pending_prompt());
4723
4724        // Cancel saving item 3.
4725        cx.simulate_prompt_answer(1);
4726        cx.executor().run_until_parked();
4727
4728        // Item 3 is reloaded. There's a prompt to save item 4.
4729        pane.update(cx, |pane, cx| {
4730            assert_eq!(item3.read(cx).save_count, 0);
4731            assert_eq!(item3.read(cx).save_as_count, 0);
4732            assert_eq!(item3.read(cx).reload_count, 1);
4733            assert_eq!(pane.items_len(), 2);
4734            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4735        });
4736        assert!(cx.has_pending_prompt());
4737
4738        // Confirm saving item 4.
4739        cx.simulate_prompt_answer(0);
4740        cx.executor().run_until_parked();
4741
4742        // There's a prompt for a path for item 4.
4743        cx.simulate_new_path_selection(|_| Some(Default::default()));
4744        close_items.await.unwrap();
4745
4746        // The requested items are closed.
4747        pane.update(cx, |pane, cx| {
4748            assert_eq!(item4.read(cx).save_count, 0);
4749            assert_eq!(item4.read(cx).save_as_count, 1);
4750            assert_eq!(item4.read(cx).reload_count, 0);
4751            assert_eq!(pane.items_len(), 1);
4752            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4753        });
4754    }
4755
4756    #[gpui::test]
4757    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4758        init_test(cx);
4759
4760        let fs = FakeFs::new(cx.executor());
4761        let project = Project::test(fs, [], cx).await;
4762        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4763
4764        // Create several workspace items with single project entries, and two
4765        // workspace items with multiple project entries.
4766        let single_entry_items = (0..=4)
4767            .map(|project_entry_id| {
4768                cx.build_view(|cx| {
4769                    TestItem::new(cx)
4770                        .with_dirty(true)
4771                        .with_project_items(&[TestProjectItem::new(
4772                            project_entry_id,
4773                            &format!("{project_entry_id}.txt"),
4774                            cx,
4775                        )])
4776                })
4777            })
4778            .collect::<Vec<_>>();
4779        let item_2_3 = cx.build_view(|cx| {
4780            TestItem::new(cx)
4781                .with_dirty(true)
4782                .with_singleton(false)
4783                .with_project_items(&[
4784                    single_entry_items[2].read(cx).project_items[0].clone(),
4785                    single_entry_items[3].read(cx).project_items[0].clone(),
4786                ])
4787        });
4788        let item_3_4 = cx.build_view(|cx| {
4789            TestItem::new(cx)
4790                .with_dirty(true)
4791                .with_singleton(false)
4792                .with_project_items(&[
4793                    single_entry_items[3].read(cx).project_items[0].clone(),
4794                    single_entry_items[4].read(cx).project_items[0].clone(),
4795                ])
4796        });
4797
4798        // Create two panes that contain the following project entries:
4799        //   left pane:
4800        //     multi-entry items:   (2, 3)
4801        //     single-entry items:  0, 1, 2, 3, 4
4802        //   right pane:
4803        //     single-entry items:  1
4804        //     multi-entry items:   (3, 4)
4805        let left_pane = workspace.update(cx, |workspace, cx| {
4806            let left_pane = workspace.active_pane().clone();
4807            workspace.add_item(Box::new(item_2_3.clone()), cx);
4808            for item in single_entry_items {
4809                workspace.add_item(Box::new(item), cx);
4810            }
4811            left_pane.update(cx, |pane, cx| {
4812                pane.activate_item(2, true, true, cx);
4813            });
4814
4815            let right_pane = workspace
4816                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4817                .unwrap();
4818
4819            right_pane.update(cx, |pane, cx| {
4820                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4821            });
4822
4823            left_pane
4824        });
4825
4826        cx.focus_view(&left_pane);
4827
4828        // When closing all of the items in the left pane, we should be prompted twice:
4829        // once for project entry 0, and once for project entry 2. Project entries 1,
4830        // 3, and 4 are all still open in the other paten. After those two
4831        // prompts, the task should complete.
4832
4833        let close = left_pane.update(cx, |pane, cx| {
4834            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4835        });
4836        cx.executor().run_until_parked();
4837
4838        // Discard "Save all" prompt
4839        cx.simulate_prompt_answer(2);
4840
4841        cx.executor().run_until_parked();
4842        left_pane.update(cx, |pane, cx| {
4843            assert_eq!(
4844                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4845                &[ProjectEntryId::from_proto(0)]
4846            );
4847        });
4848        cx.simulate_prompt_answer(0);
4849
4850        cx.executor().run_until_parked();
4851        left_pane.update(cx, |pane, cx| {
4852            assert_eq!(
4853                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4854                &[ProjectEntryId::from_proto(2)]
4855            );
4856        });
4857        cx.simulate_prompt_answer(0);
4858
4859        cx.executor().run_until_parked();
4860        close.await.unwrap();
4861        left_pane.update(cx, |pane, _| {
4862            assert_eq!(pane.items_len(), 0);
4863        });
4864    }
4865
4866    #[gpui::test]
4867    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4868        init_test(cx);
4869
4870        let fs = FakeFs::new(cx.executor());
4871        let project = Project::test(fs, [], cx).await;
4872        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4873        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4874
4875        let item = cx.build_view(|cx| {
4876            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4877        });
4878        let item_id = item.entity_id();
4879        workspace.update(cx, |workspace, cx| {
4880            workspace.add_item(Box::new(item.clone()), cx);
4881        });
4882
4883        // Autosave on window change.
4884        item.update(cx, |item, cx| {
4885            cx.update_global(|settings: &mut SettingsStore, cx| {
4886                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4887                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4888                })
4889            });
4890            item.is_dirty = true;
4891        });
4892
4893        // Deactivating the window saves the file.
4894        cx.simulate_deactivation();
4895        cx.executor().run_until_parked();
4896        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4897
4898        // Autosave on focus change.
4899        item.update(cx, |item, cx| {
4900            cx.focus_self();
4901            cx.update_global(|settings: &mut SettingsStore, cx| {
4902                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4903                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4904                })
4905            });
4906            item.is_dirty = true;
4907        });
4908
4909        // Blurring the item saves the file.
4910        item.update(cx, |_, cx| cx.blur());
4911        cx.executor().run_until_parked();
4912        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4913
4914        // Deactivating the window still saves the file.
4915        cx.simulate_activation();
4916        item.update(cx, |item, cx| {
4917            cx.focus_self();
4918            item.is_dirty = true;
4919        });
4920        cx.simulate_deactivation();
4921
4922        cx.executor().run_until_parked();
4923        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4924
4925        // Autosave after delay.
4926        item.update(cx, |item, cx| {
4927            cx.update_global(|settings: &mut SettingsStore, cx| {
4928                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4929                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4930                })
4931            });
4932            item.is_dirty = true;
4933            cx.emit(ItemEvent::Edit);
4934        });
4935
4936        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4937        cx.executor().advance_clock(Duration::from_millis(250));
4938        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4939
4940        // After delay expires, the file is saved.
4941        cx.executor().advance_clock(Duration::from_millis(250));
4942        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4943
4944        // Autosave on focus change, ensuring closing the tab counts as such.
4945        item.update(cx, |item, cx| {
4946            cx.update_global(|settings: &mut SettingsStore, cx| {
4947                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4948                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4949                })
4950            });
4951            item.is_dirty = true;
4952        });
4953
4954        pane.update(cx, |pane, cx| {
4955            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4956        })
4957        .await
4958        .unwrap();
4959        assert!(!cx.has_pending_prompt());
4960        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4961
4962        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4963        workspace.update(cx, |workspace, cx| {
4964            workspace.add_item(Box::new(item.clone()), cx);
4965        });
4966        item.update(cx, |item, cx| {
4967            item.project_items[0].update(cx, |item, _| {
4968                item.entry_id = None;
4969            });
4970            item.is_dirty = true;
4971            cx.blur();
4972        });
4973        cx.executor().run_until_parked();
4974        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4975
4976        // Ensure autosave is prevented for deleted files also when closing the buffer.
4977        let _close_items = pane.update(cx, |pane, cx| {
4978            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4979        });
4980        cx.executor().run_until_parked();
4981        assert!(cx.has_pending_prompt());
4982        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4983    }
4984
4985    #[gpui::test]
4986    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4987        init_test(cx);
4988
4989        let fs = FakeFs::new(cx.executor());
4990
4991        let project = Project::test(fs, [], cx).await;
4992        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4993
4994        let item = cx.build_view(|cx| {
4995            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4996        });
4997        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4998        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4999        let toolbar_notify_count = Rc::new(RefCell::new(0));
5000
5001        workspace.update(cx, |workspace, cx| {
5002            workspace.add_item(Box::new(item.clone()), cx);
5003            let toolbar_notification_count = toolbar_notify_count.clone();
5004            cx.observe(&toolbar, move |_, _, _| {
5005                *toolbar_notification_count.borrow_mut() += 1
5006            })
5007            .detach();
5008        });
5009
5010        pane.update(cx, |pane, _| {
5011            assert!(!pane.can_navigate_backward());
5012            assert!(!pane.can_navigate_forward());
5013        });
5014
5015        item.update(cx, |item, cx| {
5016            item.set_state("one".to_string(), cx);
5017        });
5018
5019        // Toolbar must be notified to re-render the navigation buttons
5020        assert_eq!(*toolbar_notify_count.borrow(), 1);
5021
5022        pane.update(cx, |pane, _| {
5023            assert!(pane.can_navigate_backward());
5024            assert!(!pane.can_navigate_forward());
5025        });
5026
5027        workspace
5028            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5029            .await
5030            .unwrap();
5031
5032        assert_eq!(*toolbar_notify_count.borrow(), 2);
5033        pane.update(cx, |pane, _| {
5034            assert!(!pane.can_navigate_backward());
5035            assert!(pane.can_navigate_forward());
5036        });
5037    }
5038
5039    //     #[gpui::test]
5040    //     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5041    //         init_test(cx);
5042    //         let fs = FakeFs::new(cx.executor());
5043
5044    //         let project = Project::test(fs, [], cx).await;
5045    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5046    //         let workspace = window.root(cx);
5047
5048    //         let panel = workspace.update(cx, |workspace, cx| {
5049    //             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5050    //             workspace.add_panel(panel.clone(), cx);
5051
5052    //             workspace
5053    //                 .right_dock()
5054    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5055
5056    //             panel
5057    //         });
5058
5059    //         let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5060    //         pane.update(cx, |pane, cx| {
5061    //             let item = cx.build_view(|_| TestItem::new(cx));
5062    //             pane.add_item(Box::new(item), true, true, None, cx);
5063    //         });
5064
5065    //         // Transfer focus from center to panel
5066    //         workspace.update(cx, |workspace, cx| {
5067    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5068    //         });
5069
5070    //         workspace.update(cx, |workspace, cx| {
5071    //             assert!(workspace.right_dock().read(cx).is_open());
5072    //             assert!(!panel.is_zoomed(cx));
5073    //             assert!(panel.has_focus(cx));
5074    //         });
5075
5076    //         // Transfer focus from panel to center
5077    //         workspace.update(cx, |workspace, cx| {
5078    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5079    //         });
5080
5081    //         workspace.update(cx, |workspace, cx| {
5082    //             assert!(workspace.right_dock().read(cx).is_open());
5083    //             assert!(!panel.is_zoomed(cx));
5084    //             assert!(!panel.has_focus(cx));
5085    //         });
5086
5087    //         // Close the dock
5088    //         workspace.update(cx, |workspace, cx| {
5089    //             workspace.toggle_dock(DockPosition::Right, cx);
5090    //         });
5091
5092    //         workspace.update(cx, |workspace, cx| {
5093    //             assert!(!workspace.right_dock().read(cx).is_open());
5094    //             assert!(!panel.is_zoomed(cx));
5095    //             assert!(!panel.has_focus(cx));
5096    //         });
5097
5098    //         // Open the dock
5099    //         workspace.update(cx, |workspace, cx| {
5100    //             workspace.toggle_dock(DockPosition::Right, cx);
5101    //         });
5102
5103    //         workspace.update(cx, |workspace, cx| {
5104    //             assert!(workspace.right_dock().read(cx).is_open());
5105    //             assert!(!panel.is_zoomed(cx));
5106    //             assert!(panel.has_focus(cx));
5107    //         });
5108
5109    //         // Focus and zoom panel
5110    //         panel.update(cx, |panel, cx| {
5111    //             cx.focus_self();
5112    //             panel.set_zoomed(true, cx)
5113    //         });
5114
5115    //         workspace.update(cx, |workspace, cx| {
5116    //             assert!(workspace.right_dock().read(cx).is_open());
5117    //             assert!(panel.is_zoomed(cx));
5118    //             assert!(panel.has_focus(cx));
5119    //         });
5120
5121    //         // Transfer focus to the center closes the dock
5122    //         workspace.update(cx, |workspace, cx| {
5123    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5124    //         });
5125
5126    //         workspace.update(cx, |workspace, cx| {
5127    //             assert!(!workspace.right_dock().read(cx).is_open());
5128    //             assert!(panel.is_zoomed(cx));
5129    //             assert!(!panel.has_focus(cx));
5130    //         });
5131
5132    //         // Transferring focus back to the panel keeps it zoomed
5133    //         workspace.update(cx, |workspace, cx| {
5134    //             workspace.toggle_panel_focus::<TestPanel>(cx);
5135    //         });
5136
5137    //         workspace.update(cx, |workspace, cx| {
5138    //             assert!(workspace.right_dock().read(cx).is_open());
5139    //             assert!(panel.is_zoomed(cx));
5140    //             assert!(panel.has_focus(cx));
5141    //         });
5142
5143    //         // Close the dock while it is zoomed
5144    //         workspace.update(cx, |workspace, cx| {
5145    //             workspace.toggle_dock(DockPosition::Right, cx)
5146    //         });
5147
5148    //         workspace.update(cx, |workspace, cx| {
5149    //             assert!(!workspace.right_dock().read(cx).is_open());
5150    //             assert!(panel.is_zoomed(cx));
5151    //             assert!(workspace.zoomed.is_none());
5152    //             assert!(!panel.has_focus(cx));
5153    //         });
5154
5155    //         // Opening the dock, when it's zoomed, retains focus
5156    //         workspace.update(cx, |workspace, cx| {
5157    //             workspace.toggle_dock(DockPosition::Right, cx)
5158    //         });
5159
5160    //         workspace.update(cx, |workspace, cx| {
5161    //             assert!(workspace.right_dock().read(cx).is_open());
5162    //             assert!(panel.is_zoomed(cx));
5163    //             assert!(workspace.zoomed.is_some());
5164    //             assert!(panel.has_focus(cx));
5165    //         });
5166
5167    //         // Unzoom and close the panel, zoom the active pane.
5168    //         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5169    //         workspace.update(cx, |workspace, cx| {
5170    //             workspace.toggle_dock(DockPosition::Right, cx)
5171    //         });
5172    //         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5173
5174    //         // Opening a dock unzooms the pane.
5175    //         workspace.update(cx, |workspace, cx| {
5176    //             workspace.toggle_dock(DockPosition::Right, cx)
5177    //         });
5178    //         workspace.update(cx, |workspace, cx| {
5179    //             let pane = pane.read(cx);
5180    //             assert!(!pane.is_zoomed());
5181    //             assert!(!pane.has_focus());
5182    //             assert!(workspace.right_dock().read(cx).is_open());
5183    //             assert!(workspace.zoomed.is_none());
5184    //         });
5185    //     }
5186
5187    //     #[gpui::test]
5188    //     async fn test_panels(cx: &mut gpui::TestAppContext) {
5189    //         init_test(cx);
5190    //         let fs = FakeFs::new(cx.executor());
5191
5192    //         let project = Project::test(fs, [], cx).await;
5193    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5194    //         let workspace = window.root(cx);
5195
5196    //         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5197    //             // Add panel_1 on the left, panel_2 on the right.
5198    //             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5199    //             workspace.add_panel(panel_1.clone(), cx);
5200    //             workspace
5201    //                 .left_dock()
5202    //                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5203    //             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5204    //             workspace.add_panel(panel_2.clone(), cx);
5205    //             workspace
5206    //                 .right_dock()
5207    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5208
5209    //             let left_dock = workspace.left_dock();
5210    //             assert_eq!(
5211    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5212    //                 panel_1.id()
5213    //             );
5214    //             assert_eq!(
5215    //                 left_dock.read(cx).active_panel_size(cx).unwrap(),
5216    //                 panel_1.size(cx)
5217    //             );
5218
5219    //             left_dock.update(cx, |left_dock, cx| {
5220    //                 left_dock.resize_active_panel(Some(1337.), cx)
5221    //             });
5222    //             assert_eq!(
5223    //                 workspace
5224    //                     .right_dock()
5225    //                     .read(cx)
5226    //                     .visible_panel()
5227    //                     .unwrap()
5228    //                     .id(),
5229    //                 panel_2.id()
5230    //             );
5231
5232    //             (panel_1, panel_2)
5233    //         });
5234
5235    //         // Move panel_1 to the right
5236    //         panel_1.update(cx, |panel_1, cx| {
5237    //             panel_1.set_position(DockPosition::Right, cx)
5238    //         });
5239
5240    //         workspace.update(cx, |workspace, cx| {
5241    //             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5242    //             // Since it was the only panel on the left, the left dock should now be closed.
5243    //             assert!(!workspace.left_dock().read(cx).is_open());
5244    //             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5245    //             let right_dock = workspace.right_dock();
5246    //             assert_eq!(
5247    //                 right_dock.read(cx).visible_panel().unwrap().id(),
5248    //                 panel_1.id()
5249    //             );
5250    //             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5251
5252    //             // Now we move panel_2Β to the left
5253    //             panel_2.set_position(DockPosition::Left, cx);
5254    //         });
5255
5256    //         workspace.update(cx, |workspace, cx| {
5257    //             // Since panel_2 was not visible on the right, we don't open the left dock.
5258    //             assert!(!workspace.left_dock().read(cx).is_open());
5259    //             // And the right dock is unaffected in it's displaying of panel_1
5260    //             assert!(workspace.right_dock().read(cx).is_open());
5261    //             assert_eq!(
5262    //                 workspace
5263    //                     .right_dock()
5264    //                     .read(cx)
5265    //                     .visible_panel()
5266    //                     .unwrap()
5267    //                     .id(),
5268    //                 panel_1.id()
5269    //             );
5270    //         });
5271
5272    //         // Move panel_1 back to the left
5273    //         panel_1.update(cx, |panel_1, cx| {
5274    //             panel_1.set_position(DockPosition::Left, cx)
5275    //         });
5276
5277    //         workspace.update(cx, |workspace, cx| {
5278    //             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5279    //             let left_dock = workspace.left_dock();
5280    //             assert!(left_dock.read(cx).is_open());
5281    //             assert_eq!(
5282    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5283    //                 panel_1.id()
5284    //             );
5285    //             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5286    //             // And right the dock should be closed as it no longer has any panels.
5287    //             assert!(!workspace.right_dock().read(cx).is_open());
5288
5289    //             // Now we move panel_1 to the bottom
5290    //             panel_1.set_position(DockPosition::Bottom, cx);
5291    //         });
5292
5293    //         workspace.update(cx, |workspace, cx| {
5294    //             // Since panel_1 was visible on the left, we close the left dock.
5295    //             assert!(!workspace.left_dock().read(cx).is_open());
5296    //             // The bottom dock is sized based on the panel's default size,
5297    //             // since the panel orientation changed from vertical to horizontal.
5298    //             let bottom_dock = workspace.bottom_dock();
5299    //             assert_eq!(
5300    //                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5301    //                 panel_1.size(cx),
5302    //             );
5303    //             // Close bottom dock and move panel_1 back to the left.
5304    //             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5305    //             panel_1.set_position(DockPosition::Left, cx);
5306    //         });
5307
5308    //         // Emit activated event on panel 1
5309    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5310
5311    //         // Now the left dock is open and panel_1 is active and focused.
5312    //         workspace.update(cx, |workspace, cx| {
5313    //             let left_dock = workspace.left_dock();
5314    //             assert!(left_dock.read(cx).is_open());
5315    //             assert_eq!(
5316    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5317    //                 panel_1.id()
5318    //             );
5319    //             assert!(panel_1.is_focused(cx));
5320    //         });
5321
5322    //         // Emit closed event on panel 2, which is not active
5323    //         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5324
5325    //         // Wo don't close the left dock, because panel_2 wasn't the active panel
5326    //         workspace.update(cx, |workspace, cx| {
5327    //             let left_dock = workspace.left_dock();
5328    //             assert!(left_dock.read(cx).is_open());
5329    //             assert_eq!(
5330    //                 left_dock.read(cx).visible_panel().unwrap().id(),
5331    //                 panel_1.id()
5332    //             );
5333    //         });
5334
5335    //         // Emitting a ZoomIn event shows the panel as zoomed.
5336    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5337    //         workspace.update(cx, |workspace, _| {
5338    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5339    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5340    //         });
5341
5342    //         // Move panel to another dock while it is zoomed
5343    //         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5344    //         workspace.update(cx, |workspace, _| {
5345    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5346    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5347    //         });
5348
5349    //         // If focus is transferred to another view that's not a panel or another pane, we still show
5350    //         // the panel as zoomed.
5351    //         let focus_receiver = cx.build_view(|_| EmptyView);
5352    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
5353    //         workspace.update(cx, |workspace, _| {
5354    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5355    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5356    //         });
5357
5358    //         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5359    //         workspace.update(cx, |_, cx| cx.focus_self());
5360    //         workspace.update(cx, |workspace, _| {
5361    //             assert_eq!(workspace.zoomed, None);
5362    //             assert_eq!(workspace.zoomed_position, None);
5363    //         });
5364
5365    //         // If focus is transferred again to another view that's not a panel or a pane, we won't
5366    //         // show the panel as zoomed because it wasn't zoomed before.
5367    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
5368    //         workspace.update(cx, |workspace, _| {
5369    //             assert_eq!(workspace.zoomed, None);
5370    //             assert_eq!(workspace.zoomed_position, None);
5371    //         });
5372
5373    //         // When focus is transferred back to the panel, it is zoomed again.
5374    //         panel_1.update(cx, |_, cx| cx.focus_self());
5375    //         workspace.update(cx, |workspace, _| {
5376    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5377    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5378    //         });
5379
5380    //         // Emitting a ZoomOut event unzooms the panel.
5381    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5382    //         workspace.update(cx, |workspace, _| {
5383    //             assert_eq!(workspace.zoomed, None);
5384    //             assert_eq!(workspace.zoomed_position, None);
5385    //         });
5386
5387    //         // Emit closed event on panel 1, which is active
5388    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5389
5390    //         // Now the left dock is closed, because panel_1 was the active panel
5391    //         workspace.update(cx, |workspace, cx| {
5392    //             let right_dock = workspace.right_dock();
5393    //             assert!(!right_dock.read(cx).is_open());
5394    //         });
5395    //     }
5396
5397    pub fn init_test(cx: &mut TestAppContext) {
5398        cx.update(|cx| {
5399            let settings_store = SettingsStore::test(cx);
5400            cx.set_global(settings_store);
5401            theme::init(theme::LoadThemes::JustBase, cx);
5402            language::init(cx);
5403            crate::init_settings(cx);
5404            Project::init_settings(cx);
5405        });
5406    }
5407}