workspace.rs

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