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