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