workspace.rs

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