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