workspace2.rs

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