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