workspace.rs

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