workspace2.rs

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