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