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