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