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 fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4027    cx.update(|cx| {
4028        for window in cx.windows() {
4029            let is_workspace = window.downcast::<Workspace>().is_some();
4030            if is_workspace {
4031                window.update(cx, |_, cx| cx.activate_window()).ok();
4032                return Some(window);
4033            }
4034        }
4035        None
4036    })
4037    .ok()
4038    .flatten()
4039}
4040
4041#[allow(clippy::type_complexity)]
4042pub fn open_paths(
4043    abs_paths: &[PathBuf],
4044    app_state: &Arc<AppState>,
4045    requesting_window: Option<WindowHandle<Workspace>>,
4046    cx: &mut AppContext,
4047) -> Task<
4048    anyhow::Result<(
4049        WindowHandle<Workspace>,
4050        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4051    )>,
4052> {
4053    let app_state = app_state.clone();
4054    let abs_paths = abs_paths.to_vec();
4055    // Open paths in existing workspace if possible
4056    let existing = activate_workspace_for_project(cx, {
4057        let abs_paths = abs_paths.clone();
4058        move |project, cx| project.contains_paths(&abs_paths, cx)
4059    });
4060    cx.spawn(move |mut cx| async move {
4061        if let Some(existing) = existing {
4062            Ok((
4063                existing.clone(),
4064                existing
4065                    .update(&mut cx, |workspace, cx| {
4066                        workspace.open_paths(abs_paths, true, cx)
4067                    })?
4068                    .await,
4069            ))
4070        } else {
4071            cx.update(move |cx| {
4072                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4073            })?
4074            .await
4075        }
4076    })
4077}
4078
4079pub fn open_new(
4080    app_state: &Arc<AppState>,
4081    cx: &mut AppContext,
4082    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4083) -> Task<()> {
4084    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4085    cx.spawn(|mut cx| async move {
4086        if let Some((workspace, opened_paths)) = task.await.log_err() {
4087            workspace
4088                .update(&mut cx, |workspace, cx| {
4089                    if opened_paths.is_empty() {
4090                        init(workspace, cx)
4091                    }
4092                })
4093                .log_err();
4094        }
4095    })
4096}
4097
4098pub fn create_and_open_local_file(
4099    path: &'static Path,
4100    cx: &mut ViewContext<Workspace>,
4101    default_content: impl 'static + Send + FnOnce() -> Rope,
4102) -> Task<Result<Box<dyn ItemHandle>>> {
4103    cx.spawn(|workspace, mut cx| async move {
4104        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4105        if !fs.is_file(path).await {
4106            fs.create_file(path, Default::default()).await?;
4107            fs.save(path, &default_content(), Default::default())
4108                .await?;
4109        }
4110
4111        let mut items = workspace
4112            .update(&mut cx, |workspace, cx| {
4113                workspace.with_local_workspace(cx, |workspace, cx| {
4114                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4115                })
4116            })?
4117            .await?
4118            .await;
4119
4120        let item = items.pop().flatten();
4121        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4122    })
4123}
4124
4125pub fn join_remote_project(
4126    project_id: u64,
4127    follow_user_id: u64,
4128    app_state: Arc<AppState>,
4129    cx: &mut AppContext,
4130) -> Task<Result<()>> {
4131    let windows = cx.windows();
4132    cx.spawn(|mut cx| async move {
4133        let existing_workspace = windows.into_iter().find_map(|window| {
4134            window.downcast::<Workspace>().and_then(|window| {
4135                window
4136                    .update(&mut cx, |workspace, cx| {
4137                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4138                            Some(window)
4139                        } else {
4140                            None
4141                        }
4142                    })
4143                    .unwrap_or(None)
4144            })
4145        });
4146
4147        let workspace = if let Some(existing_workspace) = existing_workspace {
4148            existing_workspace
4149        } else {
4150            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4151            let room = active_call
4152                .read_with(&cx, |call, _| call.room().cloned())?
4153                .ok_or_else(|| anyhow!("not in a call"))?;
4154            let project = room
4155                .update(&mut cx, |room, cx| {
4156                    room.join_project(
4157                        project_id,
4158                        app_state.languages.clone(),
4159                        app_state.fs.clone(),
4160                        cx,
4161                    )
4162                })?
4163                .await?;
4164
4165            let window_bounds_override = window_bounds_env_override(&cx);
4166            cx.update(|cx| {
4167                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4168                cx.open_window(options, |cx| {
4169                    cx.build_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4170                })
4171            })?
4172        };
4173
4174        workspace.update(&mut cx, |workspace, cx| {
4175            cx.activate(true);
4176            cx.activate_window();
4177
4178            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4179                let follow_peer_id = room
4180                    .read(cx)
4181                    .remote_participants()
4182                    .iter()
4183                    .find(|(_, participant)| participant.user.id == follow_user_id)
4184                    .map(|(_, p)| p.peer_id)
4185                    .or_else(|| {
4186                        // If we couldn't follow the given user, follow the host instead.
4187                        let collaborator = workspace
4188                            .project()
4189                            .read(cx)
4190                            .collaborators()
4191                            .values()
4192                            .find(|collaborator| collaborator.replica_id == 0)?;
4193                        Some(collaborator.peer_id)
4194                    });
4195
4196                if let Some(follow_peer_id) = follow_peer_id {
4197                    workspace.follow(follow_peer_id, cx);
4198                }
4199            }
4200        })?;
4201
4202        anyhow::Ok(())
4203    })
4204}
4205
4206pub fn restart(_: &Restart, cx: &mut AppContext) {
4207    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4208    let mut workspace_windows = cx
4209        .windows()
4210        .into_iter()
4211        .filter_map(|window| window.downcast::<Workspace>())
4212        .collect::<Vec<_>>();
4213
4214    // If multiple windows have unsaved changes, and need a save prompt,
4215    // prompt in the active window before switching to a different window.
4216    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4217
4218    let mut prompt = None;
4219    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4220        prompt = window
4221            .update(cx, |_, cx| {
4222                cx.prompt(
4223                    PromptLevel::Info,
4224                    "Are you sure you want to restart?",
4225                    &["Restart", "Cancel"],
4226                )
4227            })
4228            .ok();
4229    }
4230
4231    cx.spawn(|mut cx| async move {
4232        if let Some(prompt) = prompt {
4233            let answer = prompt.await?;
4234            if answer != 0 {
4235                return Ok(());
4236            }
4237        }
4238
4239        // If the user cancels any save prompt, then keep the app open.
4240        for window in workspace_windows {
4241            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4242                workspace.prepare_to_close(true, cx)
4243            }) {
4244                if !should_close.await? {
4245                    return Ok(());
4246                }
4247            }
4248        }
4249
4250        cx.update(|cx| cx.restart())
4251    })
4252    .detach_and_log_err(cx);
4253}
4254
4255fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4256    let mut parts = value.split(',');
4257    let x: usize = parts.next()?.parse().ok()?;
4258    let y: usize = parts.next()?.parse().ok()?;
4259    Some(point((x as f64).into(), (y as f64).into()))
4260}
4261
4262fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4263    let mut parts = value.split(',');
4264    let width: usize = parts.next()?.parse().ok()?;
4265    let height: usize = parts.next()?.parse().ok()?;
4266    Some(size((width as f64).into(), (height as f64).into()))
4267}
4268
4269struct DisconnectedOverlay;
4270
4271impl Element for DisconnectedOverlay {
4272    type State = AnyElement;
4273
4274    fn layout(
4275        &mut self,
4276        _: Option<Self::State>,
4277        cx: &mut WindowContext,
4278    ) -> (LayoutId, Self::State) {
4279        let mut background = cx.theme().colors().elevated_surface_background;
4280        background.fade_out(0.2);
4281        let mut overlay = div()
4282            .bg(background)
4283            .absolute()
4284            .left_0()
4285            .top_0()
4286            .size_full()
4287            .flex()
4288            .items_center()
4289            .justify_center()
4290            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4291            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4292            .child(Label::new(
4293                "Your connection to the remote project has been lost.",
4294            ))
4295            .into_any();
4296        (overlay.layout(cx), overlay)
4297    }
4298
4299    fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
4300        cx.with_z_index(u8::MAX, |cx| {
4301            cx.add_opaque_layer(bounds);
4302            overlay.paint(cx);
4303        })
4304    }
4305}
4306
4307impl IntoElement for DisconnectedOverlay {
4308    type Element = Self;
4309
4310    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4311        None
4312    }
4313
4314    fn into_element(self) -> Self::Element {
4315        self
4316    }
4317}
4318
4319#[cfg(test)]
4320mod tests {
4321    use std::{cell::RefCell, rc::Rc};
4322
4323    use super::*;
4324    use crate::item::{
4325        test::{TestItem, TestProjectItem},
4326        ItemEvent,
4327    };
4328    use fs::FakeFs;
4329    use gpui::TestAppContext;
4330    use project::{Project, ProjectEntryId};
4331    use serde_json::json;
4332    use settings::SettingsStore;
4333
4334    #[gpui::test]
4335    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4336        init_test(cx);
4337
4338        let fs = FakeFs::new(cx.executor());
4339        let project = Project::test(fs, [], cx).await;
4340        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4341
4342        // Adding an item with no ambiguity renders the tab without detail.
4343        let item1 = cx.build_view(|cx| {
4344            let mut item = TestItem::new(cx);
4345            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4346            item
4347        });
4348        workspace.update(cx, |workspace, cx| {
4349            workspace.add_item(Box::new(item1.clone()), cx);
4350        });
4351        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4352
4353        // Adding an item that creates ambiguity increases the level of detail on
4354        // both tabs.
4355        let item2 = cx.build_view(|cx| {
4356            let mut item = TestItem::new(cx);
4357            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4358            item
4359        });
4360        workspace.update(cx, |workspace, cx| {
4361            workspace.add_item(Box::new(item2.clone()), cx);
4362        });
4363        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4364        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4365
4366        // Adding an item that creates ambiguity increases the level of detail only
4367        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4368        // we stop at the highest detail available.
4369        let item3 = cx.build_view(|cx| {
4370            let mut item = TestItem::new(cx);
4371            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4372            item
4373        });
4374        workspace.update(cx, |workspace, cx| {
4375            workspace.add_item(Box::new(item3.clone()), cx);
4376        });
4377        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4378        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4379        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4380    }
4381
4382    #[gpui::test]
4383    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4384        init_test(cx);
4385
4386        let fs = FakeFs::new(cx.executor());
4387        fs.insert_tree(
4388            "/root1",
4389            json!({
4390                "one.txt": "",
4391                "two.txt": "",
4392            }),
4393        )
4394        .await;
4395        fs.insert_tree(
4396            "/root2",
4397            json!({
4398                "three.txt": "",
4399            }),
4400        )
4401        .await;
4402
4403        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4404        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4405        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4406        let worktree_id = project.update(cx, |project, cx| {
4407            project.worktrees().next().unwrap().read(cx).id()
4408        });
4409
4410        let item1 = cx.build_view(|cx| {
4411            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4412        });
4413        let item2 = cx.build_view(|cx| {
4414            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4415        });
4416
4417        // Add an item to an empty pane
4418        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4419        project.update(cx, |project, cx| {
4420            assert_eq!(
4421                project.active_entry(),
4422                project
4423                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4424                    .map(|e| e.id)
4425            );
4426        });
4427        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4428
4429        // Add a second item to a non-empty pane
4430        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4431        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
4432        project.update(cx, |project, cx| {
4433            assert_eq!(
4434                project.active_entry(),
4435                project
4436                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4437                    .map(|e| e.id)
4438            );
4439        });
4440
4441        // Close the active item
4442        pane.update(cx, |pane, cx| {
4443            pane.close_active_item(&Default::default(), cx).unwrap()
4444        })
4445        .await
4446        .unwrap();
4447        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4448        project.update(cx, |project, cx| {
4449            assert_eq!(
4450                project.active_entry(),
4451                project
4452                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4453                    .map(|e| e.id)
4454            );
4455        });
4456
4457        // Add a project folder
4458        project
4459            .update(cx, |project, cx| {
4460                project.find_or_create_local_worktree("/root2", true, cx)
4461            })
4462            .await
4463            .unwrap();
4464        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
4465
4466        // Remove a project folder
4467        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4468        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
4469    }
4470
4471    #[gpui::test]
4472    async fn test_close_window(cx: &mut TestAppContext) {
4473        init_test(cx);
4474
4475        let fs = FakeFs::new(cx.executor());
4476        fs.insert_tree("/root", json!({ "one": "" })).await;
4477
4478        let project = Project::test(fs, ["root".as_ref()], cx).await;
4479        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4480
4481        // When there are no dirty items, there's nothing to do.
4482        let item1 = cx.build_view(|cx| TestItem::new(cx));
4483        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4484        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4485        assert!(task.await.unwrap());
4486
4487        // When there are dirty untitled items, prompt to save each one. If the user
4488        // cancels any prompt, then abort.
4489        let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
4490        let item3 = cx.build_view(|cx| {
4491            TestItem::new(cx)
4492                .with_dirty(true)
4493                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4494        });
4495        workspace.update(cx, |w, cx| {
4496            w.add_item(Box::new(item2.clone()), cx);
4497            w.add_item(Box::new(item3.clone()), cx);
4498        });
4499        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4500        cx.executor().run_until_parked();
4501        cx.simulate_prompt_answer(2); // cancel save all
4502        cx.executor().run_until_parked();
4503        cx.simulate_prompt_answer(2); // cancel save all
4504        cx.executor().run_until_parked();
4505        assert!(!cx.has_pending_prompt());
4506        assert!(!task.await.unwrap());
4507    }
4508
4509    #[gpui::test]
4510    async fn test_close_pane_items(cx: &mut TestAppContext) {
4511        init_test(cx);
4512
4513        let fs = FakeFs::new(cx.executor());
4514
4515        let project = Project::test(fs, None, cx).await;
4516        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4517
4518        let item1 = cx.build_view(|cx| {
4519            TestItem::new(cx)
4520                .with_dirty(true)
4521                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4522        });
4523        let item2 = cx.build_view(|cx| {
4524            TestItem::new(cx)
4525                .with_dirty(true)
4526                .with_conflict(true)
4527                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4528        });
4529        let item3 = cx.build_view(|cx| {
4530            TestItem::new(cx)
4531                .with_dirty(true)
4532                .with_conflict(true)
4533                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4534        });
4535        let item4 = cx.build_view(|cx| {
4536            TestItem::new(cx)
4537                .with_dirty(true)
4538                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4539        });
4540        let pane = workspace.update(cx, |workspace, cx| {
4541            workspace.add_item(Box::new(item1.clone()), cx);
4542            workspace.add_item(Box::new(item2.clone()), cx);
4543            workspace.add_item(Box::new(item3.clone()), cx);
4544            workspace.add_item(Box::new(item4.clone()), cx);
4545            workspace.active_pane().clone()
4546        });
4547
4548        let close_items = pane.update(cx, |pane, cx| {
4549            pane.activate_item(1, true, true, cx);
4550            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4551            let item1_id = item1.item_id();
4552            let item3_id = item3.item_id();
4553            let item4_id = item4.item_id();
4554            pane.close_items(cx, SaveIntent::Close, move |id| {
4555                [item1_id, item3_id, item4_id].contains(&id)
4556            })
4557        });
4558        cx.executor().run_until_parked();
4559
4560        assert!(cx.has_pending_prompt());
4561        // Ignore "Save all" prompt
4562        cx.simulate_prompt_answer(2);
4563        cx.executor().run_until_parked();
4564        // There's a prompt to save item 1.
4565        pane.update(cx, |pane, _| {
4566            assert_eq!(pane.items_len(), 4);
4567            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4568        });
4569        // Confirm saving item 1.
4570        cx.simulate_prompt_answer(0);
4571        cx.executor().run_until_parked();
4572
4573        // Item 1 is saved. There's a prompt to save item 3.
4574        pane.update(cx, |pane, cx| {
4575            assert_eq!(item1.read(cx).save_count, 1);
4576            assert_eq!(item1.read(cx).save_as_count, 0);
4577            assert_eq!(item1.read(cx).reload_count, 0);
4578            assert_eq!(pane.items_len(), 3);
4579            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4580        });
4581        assert!(cx.has_pending_prompt());
4582
4583        // Cancel saving item 3.
4584        cx.simulate_prompt_answer(1);
4585        cx.executor().run_until_parked();
4586
4587        // Item 3 is reloaded. There's a prompt to save item 4.
4588        pane.update(cx, |pane, cx| {
4589            assert_eq!(item3.read(cx).save_count, 0);
4590            assert_eq!(item3.read(cx).save_as_count, 0);
4591            assert_eq!(item3.read(cx).reload_count, 1);
4592            assert_eq!(pane.items_len(), 2);
4593            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4594        });
4595        assert!(cx.has_pending_prompt());
4596
4597        // Confirm saving item 4.
4598        cx.simulate_prompt_answer(0);
4599        cx.executor().run_until_parked();
4600
4601        // There's a prompt for a path for item 4.
4602        cx.simulate_new_path_selection(|_| Some(Default::default()));
4603        close_items.await.unwrap();
4604
4605        // The requested items are closed.
4606        pane.update(cx, |pane, cx| {
4607            assert_eq!(item4.read(cx).save_count, 0);
4608            assert_eq!(item4.read(cx).save_as_count, 1);
4609            assert_eq!(item4.read(cx).reload_count, 0);
4610            assert_eq!(pane.items_len(), 1);
4611            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4612        });
4613    }
4614
4615    #[gpui::test]
4616    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4617        init_test(cx);
4618
4619        let fs = FakeFs::new(cx.executor());
4620        let project = Project::test(fs, [], cx).await;
4621        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4622
4623        // Create several workspace items with single project entries, and two
4624        // workspace items with multiple project entries.
4625        let single_entry_items = (0..=4)
4626            .map(|project_entry_id| {
4627                cx.build_view(|cx| {
4628                    TestItem::new(cx)
4629                        .with_dirty(true)
4630                        .with_project_items(&[TestProjectItem::new(
4631                            project_entry_id,
4632                            &format!("{project_entry_id}.txt"),
4633                            cx,
4634                        )])
4635                })
4636            })
4637            .collect::<Vec<_>>();
4638        let item_2_3 = cx.build_view(|cx| {
4639            TestItem::new(cx)
4640                .with_dirty(true)
4641                .with_singleton(false)
4642                .with_project_items(&[
4643                    single_entry_items[2].read(cx).project_items[0].clone(),
4644                    single_entry_items[3].read(cx).project_items[0].clone(),
4645                ])
4646        });
4647        let item_3_4 = cx.build_view(|cx| {
4648            TestItem::new(cx)
4649                .with_dirty(true)
4650                .with_singleton(false)
4651                .with_project_items(&[
4652                    single_entry_items[3].read(cx).project_items[0].clone(),
4653                    single_entry_items[4].read(cx).project_items[0].clone(),
4654                ])
4655        });
4656
4657        // Create two panes that contain the following project entries:
4658        //   left pane:
4659        //     multi-entry items:   (2, 3)
4660        //     single-entry items:  0, 1, 2, 3, 4
4661        //   right pane:
4662        //     single-entry items:  1
4663        //     multi-entry items:   (3, 4)
4664        let left_pane = workspace.update(cx, |workspace, cx| {
4665            let left_pane = workspace.active_pane().clone();
4666            workspace.add_item(Box::new(item_2_3.clone()), cx);
4667            for item in single_entry_items {
4668                workspace.add_item(Box::new(item), cx);
4669            }
4670            left_pane.update(cx, |pane, cx| {
4671                pane.activate_item(2, true, true, cx);
4672            });
4673
4674            let right_pane = workspace
4675                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4676                .unwrap();
4677
4678            right_pane.update(cx, |pane, cx| {
4679                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4680            });
4681
4682            left_pane
4683        });
4684
4685        cx.focus_view(&left_pane);
4686
4687        // When closing all of the items in the left pane, we should be prompted twice:
4688        // once for project entry 0, and once for project entry 2. Project entries 1,
4689        // 3, and 4 are all still open in the other paten. After those two
4690        // prompts, the task should complete.
4691
4692        let close = left_pane.update(cx, |pane, cx| {
4693            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4694        });
4695        cx.executor().run_until_parked();
4696
4697        // Discard "Save all" prompt
4698        cx.simulate_prompt_answer(2);
4699
4700        cx.executor().run_until_parked();
4701        left_pane.update(cx, |pane, cx| {
4702            assert_eq!(
4703                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4704                &[ProjectEntryId::from_proto(0)]
4705            );
4706        });
4707        cx.simulate_prompt_answer(0);
4708
4709        cx.executor().run_until_parked();
4710        left_pane.update(cx, |pane, cx| {
4711            assert_eq!(
4712                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4713                &[ProjectEntryId::from_proto(2)]
4714            );
4715        });
4716        cx.simulate_prompt_answer(0);
4717
4718        cx.executor().run_until_parked();
4719        close.await.unwrap();
4720        left_pane.update(cx, |pane, _| {
4721            assert_eq!(pane.items_len(), 0);
4722        });
4723    }
4724
4725    #[gpui::test]
4726    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4727        init_test(cx);
4728
4729        let fs = FakeFs::new(cx.executor());
4730        let project = Project::test(fs, [], cx).await;
4731        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4732        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4733
4734        let item = cx.build_view(|cx| {
4735            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4736        });
4737        let item_id = item.entity_id();
4738        workspace.update(cx, |workspace, cx| {
4739            workspace.add_item(Box::new(item.clone()), cx);
4740        });
4741
4742        // Autosave on window change.
4743        item.update(cx, |item, cx| {
4744            cx.update_global(|settings: &mut SettingsStore, cx| {
4745                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4746                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4747                })
4748            });
4749            item.is_dirty = true;
4750        });
4751
4752        // Deactivating the window saves the file.
4753        cx.simulate_deactivation();
4754        cx.executor().run_until_parked();
4755        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4756
4757        // Autosave on focus change.
4758        item.update(cx, |item, cx| {
4759            cx.focus_self();
4760            cx.update_global(|settings: &mut SettingsStore, cx| {
4761                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4762                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4763                })
4764            });
4765            item.is_dirty = true;
4766        });
4767
4768        // Blurring the item saves the file.
4769        item.update(cx, |_, cx| cx.blur());
4770        cx.executor().run_until_parked();
4771        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4772
4773        // Deactivating the window still saves the file.
4774        cx.simulate_activation();
4775        item.update(cx, |item, cx| {
4776            cx.focus_self();
4777            item.is_dirty = true;
4778        });
4779        cx.simulate_deactivation();
4780
4781        cx.executor().run_until_parked();
4782        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4783
4784        // Autosave after delay.
4785        item.update(cx, |item, cx| {
4786            cx.update_global(|settings: &mut SettingsStore, cx| {
4787                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4788                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4789                })
4790            });
4791            item.is_dirty = true;
4792            cx.emit(ItemEvent::Edit);
4793        });
4794
4795        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4796        cx.executor().advance_clock(Duration::from_millis(250));
4797        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4798
4799        // After delay expires, the file is saved.
4800        cx.executor().advance_clock(Duration::from_millis(250));
4801        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4802
4803        // Autosave on focus change, ensuring closing the tab counts as such.
4804        item.update(cx, |item, cx| {
4805            cx.update_global(|settings: &mut SettingsStore, cx| {
4806                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4807                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4808                })
4809            });
4810            item.is_dirty = true;
4811        });
4812
4813        pane.update(cx, |pane, cx| {
4814            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4815        })
4816        .await
4817        .unwrap();
4818        assert!(!cx.has_pending_prompt());
4819        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4820
4821        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4822        workspace.update(cx, |workspace, cx| {
4823            workspace.add_item(Box::new(item.clone()), cx);
4824        });
4825        item.update(cx, |item, cx| {
4826            item.project_items[0].update(cx, |item, _| {
4827                item.entry_id = None;
4828            });
4829            item.is_dirty = true;
4830            cx.blur();
4831        });
4832        cx.run_until_parked();
4833        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4834
4835        // Ensure autosave is prevented for deleted files also when closing the buffer.
4836        let _close_items = pane.update(cx, |pane, cx| {
4837            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4838        });
4839        cx.run_until_parked();
4840        assert!(cx.has_pending_prompt());
4841        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4842    }
4843
4844    #[gpui::test]
4845    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4846        init_test(cx);
4847
4848        let fs = FakeFs::new(cx.executor());
4849
4850        let project = Project::test(fs, [], cx).await;
4851        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4852
4853        let item = cx.build_view(|cx| {
4854            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4855        });
4856        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4857        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4858        let toolbar_notify_count = Rc::new(RefCell::new(0));
4859
4860        workspace.update(cx, |workspace, cx| {
4861            workspace.add_item(Box::new(item.clone()), cx);
4862            let toolbar_notification_count = toolbar_notify_count.clone();
4863            cx.observe(&toolbar, move |_, _, _| {
4864                *toolbar_notification_count.borrow_mut() += 1
4865            })
4866            .detach();
4867        });
4868
4869        pane.update(cx, |pane, _| {
4870            assert!(!pane.can_navigate_backward());
4871            assert!(!pane.can_navigate_forward());
4872        });
4873
4874        item.update(cx, |item, cx| {
4875            item.set_state("one".to_string(), cx);
4876        });
4877
4878        // Toolbar must be notified to re-render the navigation buttons
4879        assert_eq!(*toolbar_notify_count.borrow(), 1);
4880
4881        pane.update(cx, |pane, _| {
4882            assert!(pane.can_navigate_backward());
4883            assert!(!pane.can_navigate_forward());
4884        });
4885
4886        workspace
4887            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4888            .await
4889            .unwrap();
4890
4891        assert_eq!(*toolbar_notify_count.borrow(), 2);
4892        pane.update(cx, |pane, _| {
4893            assert!(!pane.can_navigate_backward());
4894            assert!(pane.can_navigate_forward());
4895        });
4896    }
4897
4898    // #[gpui::test]
4899    // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4900    //     init_test(cx);
4901    //     let fs = FakeFs::new(cx.executor());
4902
4903    //     let project = Project::test(fs, [], cx).await;
4904    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4905
4906    //     let panel = workspace.update(cx, |workspace, cx| {
4907    //         let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
4908    //         workspace.add_panel(panel.clone(), cx);
4909
4910    //         workspace
4911    //             .right_dock()
4912    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4913
4914    //         panel
4915    //     });
4916
4917    //     let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4918    //     pane.update(cx, |pane, cx| {
4919    //         let item = cx.build_view(|cx| TestItem::new(cx));
4920    //         pane.add_item(Box::new(item), true, true, None, cx);
4921    //     });
4922
4923    //     // Transfer focus from center to panel
4924    //     workspace.update(cx, |workspace, cx| {
4925    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4926    //     });
4927
4928    //     workspace.update(cx, |workspace, cx| {
4929    //         assert!(workspace.right_dock().read(cx).is_open());
4930    //         assert!(!panel.is_zoomed(cx));
4931    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4932    //     });
4933
4934    //     // Transfer focus from panel to center
4935    //     workspace.update(cx, |workspace, cx| {
4936    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4937    //     });
4938
4939    //     workspace.update(cx, |workspace, cx| {
4940    //         assert!(workspace.right_dock().read(cx).is_open());
4941    //         assert!(!panel.is_zoomed(cx));
4942    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4943    //     });
4944
4945    //     // Close the dock
4946    //     workspace.update(cx, |workspace, cx| {
4947    //         workspace.toggle_dock(DockPosition::Right, cx);
4948    //     });
4949
4950    //     workspace.update(cx, |workspace, cx| {
4951    //         assert!(!workspace.right_dock().read(cx).is_open());
4952    //         assert!(!panel.is_zoomed(cx));
4953    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4954    //     });
4955
4956    //     // Open the dock
4957    //     workspace.update(cx, |workspace, cx| {
4958    //         workspace.toggle_dock(DockPosition::Right, cx);
4959    //     });
4960
4961    //     workspace.update(cx, |workspace, cx| {
4962    //         assert!(workspace.right_dock().read(cx).is_open());
4963    //         assert!(!panel.is_zoomed(cx));
4964    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4965    //     });
4966
4967    //     // Focus and zoom panel
4968    //     panel.update(cx, |panel, cx| {
4969    //         cx.focus_self();
4970    //         panel.set_zoomed(true, cx)
4971    //     });
4972
4973    //     workspace.update(cx, |workspace, cx| {
4974    //         assert!(workspace.right_dock().read(cx).is_open());
4975    //         assert!(panel.is_zoomed(cx));
4976    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4977    //     });
4978
4979    //     // Transfer focus to the center closes the dock
4980    //     workspace.update(cx, |workspace, cx| {
4981    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4982    //     });
4983
4984    //     workspace.update(cx, |workspace, cx| {
4985    //         assert!(!workspace.right_dock().read(cx).is_open());
4986    //         assert!(panel.is_zoomed(cx));
4987    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4988    //     });
4989
4990    //     // Transferring focus back to the panel keeps it zoomed
4991    //     workspace.update(cx, |workspace, cx| {
4992    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4993    //     });
4994
4995    //     workspace.update(cx, |workspace, cx| {
4996    //         assert!(workspace.right_dock().read(cx).is_open());
4997    //         assert!(panel.is_zoomed(cx));
4998    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4999    //     });
5000
5001    //     // Close the dock while it is zoomed
5002    //     workspace.update(cx, |workspace, cx| {
5003    //         workspace.toggle_dock(DockPosition::Right, cx)
5004    //     });
5005
5006    //     workspace.update(cx, |workspace, cx| {
5007    //         assert!(!workspace.right_dock().read(cx).is_open());
5008    //         assert!(panel.is_zoomed(cx));
5009    //         assert!(workspace.zoomed.is_none());
5010    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5011    //     });
5012
5013    //     // Opening the dock, when it's zoomed, retains focus
5014    //     workspace.update(cx, |workspace, cx| {
5015    //         workspace.toggle_dock(DockPosition::Right, cx)
5016    //     });
5017
5018    //     workspace.update(cx, |workspace, cx| {
5019    //         assert!(workspace.right_dock().read(cx).is_open());
5020    //         assert!(panel.is_zoomed(cx));
5021    //         assert!(workspace.zoomed.is_some());
5022    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5023    //     });
5024
5025    //     // Unzoom and close the panel, zoom the active pane.
5026    //     panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5027    //     workspace.update(cx, |workspace, cx| {
5028    //         workspace.toggle_dock(DockPosition::Right, cx)
5029    //     });
5030    //     pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5031
5032    //     // Opening a dock unzooms the pane.
5033    //     workspace.update(cx, |workspace, cx| {
5034    //         workspace.toggle_dock(DockPosition::Right, cx)
5035    //     });
5036    //     workspace.update(cx, |workspace, cx| {
5037    //         let pane = pane.read(cx);
5038    //         assert!(!pane.is_zoomed());
5039    //         assert!(!pane.focus_handle(cx).is_focused(cx));
5040    //         assert!(workspace.right_dock().read(cx).is_open());
5041    //         assert!(workspace.zoomed.is_none());
5042    //     });
5043    // }
5044
5045    // #[gpui::test]
5046    // async fn test_panels(cx: &mut gpui::TestAppContext) {
5047    //     init_test(cx);
5048    //     let fs = FakeFs::new(cx.executor());
5049
5050    //     let project = Project::test(fs, [], cx).await;
5051    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5052
5053    //     let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5054    //         // Add panel_1 on the left, panel_2 on the right.
5055    //         let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx));
5056    //         workspace.add_panel(panel_1.clone(), cx);
5057    //         workspace
5058    //             .left_dock()
5059    //             .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5060    //         let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
5061    //         workspace.add_panel(panel_2.clone(), cx);
5062    //         workspace
5063    //             .right_dock()
5064    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5065
5066    //         let left_dock = workspace.left_dock();
5067    //         assert_eq!(
5068    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5069    //             panel_1.panel_id()
5070    //         );
5071    //         assert_eq!(
5072    //             left_dock.read(cx).active_panel_size(cx).unwrap(),
5073    //             panel_1.size(cx)
5074    //         );
5075
5076    //         left_dock.update(cx, |left_dock, cx| {
5077    //             left_dock.resize_active_panel(Some(1337.), cx)
5078    //         });
5079    //         assert_eq!(
5080    //             workspace
5081    //                 .right_dock()
5082    //                 .read(cx)
5083    //                 .visible_panel()
5084    //                 .unwrap()
5085    //                 .panel_id(),
5086    //             panel_2.panel_id(),
5087    //         );
5088
5089    //         (panel_1, panel_2)
5090    //     });
5091
5092    //     // Move panel_1 to the right
5093    //     panel_1.update(cx, |panel_1, cx| {
5094    //         panel_1.set_position(DockPosition::Right, cx)
5095    //     });
5096
5097    //     workspace.update(cx, |workspace, cx| {
5098    //         // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5099    //         // Since it was the only panel on the left, the left dock should now be closed.
5100    //         assert!(!workspace.left_dock().read(cx).is_open());
5101    //         assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5102    //         let right_dock = workspace.right_dock();
5103    //         assert_eq!(
5104    //             right_dock.read(cx).visible_panel().unwrap().panel_id(),
5105    //             panel_1.panel_id()
5106    //         );
5107    //         assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5108
5109    //         // Now we move panel_2Β to the left
5110    //         panel_2.set_position(DockPosition::Left, cx);
5111    //     });
5112
5113    //     workspace.update(cx, |workspace, cx| {
5114    //         // Since panel_2 was not visible on the right, we don't open the left dock.
5115    //         assert!(!workspace.left_dock().read(cx).is_open());
5116    //         // And the right dock is unaffected in it's displaying of panel_1
5117    //         assert!(workspace.right_dock().read(cx).is_open());
5118    //         assert_eq!(
5119    //             workspace
5120    //                 .right_dock()
5121    //                 .read(cx)
5122    //                 .visible_panel()
5123    //                 .unwrap()
5124    //                 .panel_id(),
5125    //             panel_1.panel_id(),
5126    //         );
5127    //     });
5128
5129    //     // Move panel_1 back to the left
5130    //     panel_1.update(cx, |panel_1, cx| {
5131    //         panel_1.set_position(DockPosition::Left, cx)
5132    //     });
5133
5134    //     workspace.update(cx, |workspace, cx| {
5135    //         // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5136    //         let left_dock = workspace.left_dock();
5137    //         assert!(left_dock.read(cx).is_open());
5138    //         assert_eq!(
5139    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5140    //             panel_1.panel_id()
5141    //         );
5142    //         assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5143    //         // And right the dock should be closed as it no longer has any panels.
5144    //         assert!(!workspace.right_dock().read(cx).is_open());
5145
5146    //         // Now we move panel_1 to the bottom
5147    //         panel_1.set_position(DockPosition::Bottom, cx);
5148    //     });
5149
5150    //     workspace.update(cx, |workspace, cx| {
5151    //         // Since panel_1 was visible on the left, we close the left dock.
5152    //         assert!(!workspace.left_dock().read(cx).is_open());
5153    //         // The bottom dock is sized based on the panel's default size,
5154    //         // since the panel orientation changed from vertical to horizontal.
5155    //         let bottom_dock = workspace.bottom_dock();
5156    //         assert_eq!(
5157    //             bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5158    //             panel_1.size(cx),
5159    //         );
5160    //         // Close bottom dock and move panel_1 back to the left.
5161    //         bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5162    //         panel_1.set_position(DockPosition::Left, cx);
5163    //     });
5164
5165    //     // Emit activated event on panel 1
5166    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5167
5168    //     // Now the left dock is open and panel_1 is active and focused.
5169    //     workspace.update(cx, |workspace, cx| {
5170    //         let left_dock = workspace.left_dock();
5171    //         assert!(left_dock.read(cx).is_open());
5172    //         assert_eq!(
5173    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5174    //             panel_1.panel_id(),
5175    //         );
5176    //         assert!(panel_1.focus_handle(cx).is_focused(cx));
5177    //     });
5178
5179    //     // Emit closed event on panel 2, which is not active
5180    //     panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5181
5182    //     // Wo don't close the left dock, because panel_2 wasn't the active panel
5183    //     workspace.update(cx, |workspace, cx| {
5184    //         let left_dock = workspace.left_dock();
5185    //         assert!(left_dock.read(cx).is_open());
5186    //         assert_eq!(
5187    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5188    //             panel_1.panel_id(),
5189    //         );
5190    //     });
5191
5192    //     // Emitting a ZoomIn event shows the panel as zoomed.
5193    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5194    //     workspace.update(cx, |workspace, _| {
5195    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5196    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5197    //     });
5198
5199    //     // Move panel to another dock while it is zoomed
5200    //     panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5201    //     workspace.update(cx, |workspace, _| {
5202    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5203
5204    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5205    //     });
5206
5207    //     // If focus is transferred to another view that's not a panel or another pane, we still show
5208    //     // the panel as zoomed.
5209    //     let other_focus_handle = cx.update(|cx| cx.focus_handle());
5210    //     cx.update(|cx| cx.focus(&other_focus_handle));
5211    //     workspace.update(cx, |workspace, _| {
5212    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5213    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5214    //     });
5215
5216    //     // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5217    //     workspace.update(cx, |_, cx| cx.focus_self());
5218    //     workspace.update(cx, |workspace, _| {
5219    //         assert_eq!(workspace.zoomed, None);
5220    //         assert_eq!(workspace.zoomed_position, None);
5221    //     });
5222
5223    //     // If focus is transferred again to another view that's not a panel or a pane, we won't
5224    //     // show the panel as zoomed because it wasn't zoomed before.
5225    //     cx.update(|cx| cx.focus(&other_focus_handle));
5226    //     workspace.update(cx, |workspace, _| {
5227    //         assert_eq!(workspace.zoomed, None);
5228    //         assert_eq!(workspace.zoomed_position, None);
5229    //     });
5230
5231    //     // When focus is transferred back to the panel, it is zoomed again.
5232    //     panel_1.update(cx, |_, cx| cx.focus_self());
5233    //     workspace.update(cx, |workspace, _| {
5234    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5235    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5236    //     });
5237
5238    //     // Emitting a ZoomOut event unzooms the panel.
5239    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5240    //     workspace.update(cx, |workspace, _| {
5241    //         assert_eq!(workspace.zoomed, None);
5242    //         assert_eq!(workspace.zoomed_position, None);
5243    //     });
5244
5245    //     // Emit closed event on panel 1, which is active
5246    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5247
5248    //     // Now the left dock is closed, because panel_1 was the active panel
5249    //     workspace.update(cx, |workspace, cx| {
5250    //         let right_dock = workspace.right_dock();
5251    //         assert!(!right_dock.read(cx).is_open());
5252    //     });
5253    // }
5254
5255    pub fn init_test(cx: &mut TestAppContext) {
5256        cx.update(|cx| {
5257            let settings_store = SettingsStore::test(cx);
5258            cx.set_global(settings_store);
5259            theme::init(theme::LoadThemes::JustBase, cx);
5260            language::init(cx);
5261            crate::init_settings(cx);
5262            Project::init_settings(cx);
5263        });
5264    }
5265}