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