workspace.rs

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