workspace2.rs

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