workspace.rs

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