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