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| {
1312                    this.save_all_internal(SaveBehavior::PromptOnWrite, cx)
1313                })?
1314                .await?)
1315        })
1316    }
1317
1318    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1319        let save_all = self.save_all_internal(SaveBehavior::PromptOnConflict, cx);
1320        Some(cx.foreground().spawn(async move {
1321            save_all.await?;
1322            Ok(())
1323        }))
1324    }
1325
1326    fn save_all_internal(
1327        &mut self,
1328        save_behaviour: SaveBehavior,
1329        cx: &mut ViewContext<Self>,
1330    ) -> Task<Result<bool>> {
1331        if self.project.read(cx).is_read_only() {
1332            return Task::ready(Ok(true));
1333        }
1334
1335        let dirty_items = self
1336            .panes
1337            .iter()
1338            .flat_map(|pane| {
1339                pane.read(cx).items().filter_map(|item| {
1340                    if item.is_dirty(cx) {
1341                        Some((pane.downgrade(), item.boxed_clone()))
1342                    } else {
1343                        None
1344                    }
1345                })
1346            })
1347            .collect::<Vec<_>>();
1348
1349        let project = self.project.clone();
1350        cx.spawn(|_, mut cx| async move {
1351            for (pane, item) in dirty_items {
1352                let (singleton, project_entry_ids) =
1353                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1354                if singleton || !project_entry_ids.is_empty() {
1355                    if let Some(ix) =
1356                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1357                    {
1358                        if !Pane::save_item(
1359                            project.clone(),
1360                            &pane,
1361                            ix,
1362                            &*item,
1363                            save_behaviour,
1364                            &mut cx,
1365                        )
1366                        .await?
1367                        {
1368                            return Ok(false);
1369                        }
1370                    }
1371                }
1372            }
1373            Ok(true)
1374        })
1375    }
1376
1377    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1378        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1379            files: true,
1380            directories: true,
1381            multiple: true,
1382        });
1383
1384        Some(cx.spawn(|this, mut cx| async move {
1385            if let Some(paths) = paths.recv().await.flatten() {
1386                if let Some(task) = this
1387                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1388                    .log_err()
1389                {
1390                    task.await?
1391                }
1392            }
1393            Ok(())
1394        }))
1395    }
1396
1397    pub fn open_workspace_for_paths(
1398        &mut self,
1399        paths: Vec<PathBuf>,
1400        cx: &mut ViewContext<Self>,
1401    ) -> Task<Result<()>> {
1402        let window = cx.window().downcast::<Self>();
1403        let is_remote = self.project.read(cx).is_remote();
1404        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1405        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1406        let close_task = if is_remote || has_worktree || has_dirty_items {
1407            None
1408        } else {
1409            Some(self.prepare_to_close(false, cx))
1410        };
1411        let app_state = self.app_state.clone();
1412
1413        cx.spawn(|_, mut cx| async move {
1414            let window_to_replace = if let Some(close_task) = close_task {
1415                if !close_task.await? {
1416                    return Ok(());
1417                }
1418                window
1419            } else {
1420                None
1421            };
1422            cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
1423                .await?;
1424            Ok(())
1425        })
1426    }
1427
1428    #[allow(clippy::type_complexity)]
1429    pub fn open_paths(
1430        &mut self,
1431        mut abs_paths: Vec<PathBuf>,
1432        visible: bool,
1433        cx: &mut ViewContext<Self>,
1434    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1435        log::info!("open paths {:?}", abs_paths);
1436
1437        let fs = self.app_state.fs.clone();
1438
1439        // Sort the paths to ensure we add worktrees for parents before their children.
1440        abs_paths.sort_unstable();
1441        cx.spawn(|this, mut cx| async move {
1442            let mut tasks = Vec::with_capacity(abs_paths.len());
1443            for abs_path in &abs_paths {
1444                let project_path = match this
1445                    .update(&mut cx, |this, cx| {
1446                        Workspace::project_path_for_path(
1447                            this.project.clone(),
1448                            abs_path,
1449                            visible,
1450                            cx,
1451                        )
1452                    })
1453                    .log_err()
1454                {
1455                    Some(project_path) => project_path.await.log_err(),
1456                    None => None,
1457                };
1458
1459                let this = this.clone();
1460                let task = cx.spawn(|mut cx| {
1461                    let fs = fs.clone();
1462                    let abs_path = abs_path.clone();
1463                    async move {
1464                        let (worktree, project_path) = project_path?;
1465                        if fs.is_file(&abs_path).await {
1466                            Some(
1467                                this.update(&mut cx, |this, cx| {
1468                                    this.open_path(project_path, None, true, cx)
1469                                })
1470                                .log_err()?
1471                                .await,
1472                            )
1473                        } else {
1474                            this.update(&mut cx, |workspace, cx| {
1475                                let worktree = worktree.read(cx);
1476                                let worktree_abs_path = worktree.abs_path();
1477                                let entry_id = if abs_path == worktree_abs_path.as_ref() {
1478                                    worktree.root_entry()
1479                                } else {
1480                                    abs_path
1481                                        .strip_prefix(worktree_abs_path.as_ref())
1482                                        .ok()
1483                                        .and_then(|relative_path| {
1484                                            worktree.entry_for_path(relative_path)
1485                                        })
1486                                }
1487                                .map(|entry| entry.id);
1488                                if let Some(entry_id) = entry_id {
1489                                    workspace.project().update(cx, |_, cx| {
1490                                        cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1491                                    })
1492                                }
1493                            })
1494                            .log_err()?;
1495                            None
1496                        }
1497                    }
1498                });
1499                tasks.push(task);
1500            }
1501
1502            futures::future::join_all(tasks).await
1503        })
1504    }
1505
1506    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1507        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1508            files: false,
1509            directories: true,
1510            multiple: true,
1511        });
1512        cx.spawn(|this, mut cx| async move {
1513            if let Some(paths) = paths.recv().await.flatten() {
1514                let results = this
1515                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1516                    .await;
1517                for result in results.into_iter().flatten() {
1518                    result.log_err();
1519                }
1520            }
1521            anyhow::Ok(())
1522        })
1523        .detach_and_log_err(cx);
1524    }
1525
1526    fn project_path_for_path(
1527        project: ModelHandle<Project>,
1528        abs_path: &Path,
1529        visible: bool,
1530        cx: &mut AppContext,
1531    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1532        let entry = project.update(cx, |project, cx| {
1533            project.find_or_create_local_worktree(abs_path, visible, cx)
1534        });
1535        cx.spawn(|cx| async move {
1536            let (worktree, path) = entry.await?;
1537            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1538            Ok((
1539                worktree,
1540                ProjectPath {
1541                    worktree_id,
1542                    path: path.into(),
1543                },
1544            ))
1545        })
1546    }
1547
1548    /// Returns the modal that was toggled closed if it was open.
1549    pub fn toggle_modal<V, F>(
1550        &mut self,
1551        cx: &mut ViewContext<Self>,
1552        add_view: F,
1553    ) -> Option<ViewHandle<V>>
1554    where
1555        V: 'static + Modal,
1556        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1557    {
1558        cx.notify();
1559        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1560        // it. Otherwise, create a new modal and set it as active.
1561        if let Some(already_open_modal) = self
1562            .dismiss_modal(cx)
1563            .and_then(|modal| modal.downcast::<V>())
1564        {
1565            cx.focus_self();
1566            Some(already_open_modal)
1567        } else {
1568            let modal = add_view(self, cx);
1569            cx.subscribe(&modal, |this, _, event, cx| {
1570                if V::dismiss_on_event(event) {
1571                    this.dismiss_modal(cx);
1572                }
1573            })
1574            .detach();
1575            let previously_focused_view_id = cx.focused_view_id();
1576            cx.focus(&modal);
1577            self.modal = Some(ActiveModal {
1578                view: Box::new(modal),
1579                previously_focused_view_id,
1580            });
1581            None
1582        }
1583    }
1584
1585    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1586        self.modal
1587            .as_ref()
1588            .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
1589    }
1590
1591    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
1592        if let Some(modal) = self.modal.take() {
1593            if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
1594                if modal.view.has_focus(cx) {
1595                    cx.window_context().focus(Some(previously_focused_view_id));
1596                }
1597            }
1598            cx.notify();
1599            Some(modal.view.as_any().clone())
1600        } else {
1601            None
1602        }
1603    }
1604
1605    pub fn items<'a>(
1606        &'a self,
1607        cx: &'a AppContext,
1608    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1609        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1610    }
1611
1612    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1613        self.items_of_type(cx).max_by_key(|item| item.id())
1614    }
1615
1616    pub fn items_of_type<'a, T: Item>(
1617        &'a self,
1618        cx: &'a AppContext,
1619    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1620        self.panes
1621            .iter()
1622            .flat_map(|pane| pane.read(cx).items_of_type())
1623    }
1624
1625    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1626        self.active_pane().read(cx).active_item()
1627    }
1628
1629    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1630        self.active_item(cx).and_then(|item| item.project_path(cx))
1631    }
1632
1633    pub fn save_active_item(
1634        &mut self,
1635        force_name_change: bool,
1636        cx: &mut ViewContext<Self>,
1637    ) -> Task<Result<()>> {
1638        let project = self.project.clone();
1639        if let Some(item) = self.active_item(cx) {
1640            if !force_name_change && item.can_save(cx) {
1641                if item.has_conflict(cx) {
1642                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1643
1644                    let mut answer = cx.prompt(
1645                        PromptLevel::Warning,
1646                        CONFLICT_MESSAGE,
1647                        &["Overwrite", "Cancel"],
1648                    );
1649                    cx.spawn(|this, mut cx| async move {
1650                        let answer = answer.recv().await;
1651                        if answer == Some(0) {
1652                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
1653                                .await?;
1654                        }
1655                        Ok(())
1656                    })
1657                } else {
1658                    item.save(self.project.clone(), cx)
1659                }
1660            } else if item.is_singleton(cx) {
1661                let worktree = self.worktrees(cx).next();
1662                let start_abs_path = worktree
1663                    .and_then(|w| w.read(cx).as_local())
1664                    .map_or(Path::new(""), |w| w.abs_path())
1665                    .to_path_buf();
1666                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1667                cx.spawn(|this, mut cx| async move {
1668                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1669                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
1670                            .await?;
1671                    }
1672                    Ok(())
1673                })
1674            } else {
1675                Task::ready(Ok(()))
1676            }
1677        } else {
1678            Task::ready(Ok(()))
1679        }
1680    }
1681
1682    pub fn close_inactive_items_and_panes(
1683        &mut self,
1684        _: &CloseInactiveTabsAndPanes,
1685        cx: &mut ViewContext<Self>,
1686    ) -> Option<Task<Result<()>>> {
1687        let current_pane = self.active_pane();
1688
1689        let mut tasks = Vec::new();
1690
1691        if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1692            pane.close_inactive_items(&CloseInactiveItems, cx)
1693        }) {
1694            tasks.push(current_pane_close);
1695        };
1696
1697        for pane in self.panes() {
1698            if pane.id() == current_pane.id() {
1699                continue;
1700            }
1701
1702            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1703                pane.close_all_items(&CloseAllItems, cx)
1704            }) {
1705                tasks.push(close_pane_items)
1706            }
1707        }
1708
1709        if tasks.is_empty() {
1710            None
1711        } else {
1712            Some(cx.spawn(|_, _| async move {
1713                for task in tasks {
1714                    task.await?
1715                }
1716                Ok(())
1717            }))
1718        }
1719    }
1720
1721    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1722        let dock = match dock_side {
1723            DockPosition::Left => &self.left_dock,
1724            DockPosition::Bottom => &self.bottom_dock,
1725            DockPosition::Right => &self.right_dock,
1726        };
1727        let mut focus_center = false;
1728        let mut reveal_dock = false;
1729        dock.update(cx, |dock, cx| {
1730            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1731            let was_visible = dock.is_open() && !other_is_zoomed;
1732            dock.set_open(!was_visible, cx);
1733
1734            if let Some(active_panel) = dock.active_panel() {
1735                if was_visible {
1736                    if active_panel.has_focus(cx) {
1737                        focus_center = true;
1738                    }
1739                } else {
1740                    cx.focus(active_panel.as_any());
1741                    reveal_dock = true;
1742                }
1743            }
1744        });
1745
1746        if reveal_dock {
1747            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1748        }
1749
1750        if focus_center {
1751            cx.focus_self();
1752        }
1753
1754        cx.notify();
1755        self.serialize_workspace(cx);
1756    }
1757
1758    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1759        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1760
1761        for dock in docks {
1762            dock.update(cx, |dock, cx| {
1763                dock.set_open(false, cx);
1764            });
1765        }
1766
1767        cx.focus_self();
1768        cx.notify();
1769        self.serialize_workspace(cx);
1770    }
1771
1772    /// Transfer focus to the panel of the given type.
1773    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1774        self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
1775            .as_any()
1776            .clone()
1777            .downcast()
1778    }
1779
1780    /// Focus the panel of the given type if it isn't already focused. If it is
1781    /// already focused, then transfer focus back to the workspace center.
1782    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1783        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1784    }
1785
1786    /// Focus or unfocus the given panel type, depending on the given callback.
1787    fn focus_or_unfocus_panel<T: Panel>(
1788        &mut self,
1789        cx: &mut ViewContext<Self>,
1790        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1791    ) -> Option<Rc<dyn PanelHandle>> {
1792        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1793            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1794                let mut focus_center = false;
1795                let mut reveal_dock = false;
1796                let panel = dock.update(cx, |dock, cx| {
1797                    dock.activate_panel(panel_index, cx);
1798
1799                    let panel = dock.active_panel().cloned();
1800                    if let Some(panel) = panel.as_ref() {
1801                        if should_focus(&**panel, cx) {
1802                            dock.set_open(true, cx);
1803                            cx.focus(panel.as_any());
1804                            reveal_dock = true;
1805                        } else {
1806                            // if panel.is_zoomed(cx) {
1807                            //     dock.set_open(false, cx);
1808                            // }
1809                            focus_center = true;
1810                        }
1811                    }
1812                    panel
1813                });
1814
1815                if focus_center {
1816                    cx.focus_self();
1817                }
1818
1819                self.serialize_workspace(cx);
1820                cx.notify();
1821                return panel;
1822            }
1823        }
1824        None
1825    }
1826
1827    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
1828        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1829            let dock = dock.read(cx);
1830            if let Some(panel) = dock.panel::<T>() {
1831                return Some(panel);
1832            }
1833        }
1834        None
1835    }
1836
1837    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1838        for pane in &self.panes {
1839            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1840        }
1841
1842        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1843        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1844        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1845        self.zoomed = None;
1846        self.zoomed_position = None;
1847
1848        cx.notify();
1849    }
1850
1851    #[cfg(any(test, feature = "test-support"))]
1852    pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1853        self.zoomed.and_then(|view| view.upgrade(cx))
1854    }
1855
1856    fn dismiss_zoomed_items_to_reveal(
1857        &mut self,
1858        dock_to_reveal: Option<DockPosition>,
1859        cx: &mut ViewContext<Self>,
1860    ) {
1861        // If a center pane is zoomed, unzoom it.
1862        for pane in &self.panes {
1863            if pane != &self.active_pane || dock_to_reveal.is_some() {
1864                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1865            }
1866        }
1867
1868        // If another dock is zoomed, hide it.
1869        let mut focus_center = false;
1870        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1871            dock.update(cx, |dock, cx| {
1872                if Some(dock.position()) != dock_to_reveal {
1873                    if let Some(panel) = dock.active_panel() {
1874                        if panel.is_zoomed(cx) {
1875                            focus_center |= panel.has_focus(cx);
1876                            dock.set_open(false, cx);
1877                        }
1878                    }
1879                }
1880            });
1881        }
1882
1883        if focus_center {
1884            cx.focus_self();
1885        }
1886
1887        if self.zoomed_position != dock_to_reveal {
1888            self.zoomed = None;
1889            self.zoomed_position = None;
1890        }
1891
1892        cx.notify();
1893    }
1894
1895    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1896        let pane = cx.add_view(|cx| {
1897            Pane::new(
1898                self.weak_handle(),
1899                self.project.clone(),
1900                self.app_state.background_actions,
1901                self.pane_history_timestamp.clone(),
1902                cx,
1903            )
1904        });
1905        cx.subscribe(&pane, Self::handle_pane_event).detach();
1906        self.panes.push(pane.clone());
1907        cx.focus(&pane);
1908        cx.emit(Event::PaneAdded(pane.clone()));
1909        pane
1910    }
1911
1912    pub fn add_item_to_center(
1913        &mut self,
1914        item: Box<dyn ItemHandle>,
1915        cx: &mut ViewContext<Self>,
1916    ) -> bool {
1917        if let Some(center_pane) = self.last_active_center_pane.clone() {
1918            if let Some(center_pane) = center_pane.upgrade(cx) {
1919                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1920                true
1921            } else {
1922                false
1923            }
1924        } else {
1925            false
1926        }
1927    }
1928
1929    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1930        self.active_pane
1931            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1932    }
1933
1934    pub fn split_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1935        let new_pane = self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1936        new_pane.update(cx, move |new_pane, cx| {
1937            new_pane.add_item(item, true, true, None, cx)
1938        })
1939    }
1940
1941    pub fn open_abs_path(
1942        &mut self,
1943        abs_path: PathBuf,
1944        visible: bool,
1945        cx: &mut ViewContext<Self>,
1946    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1947        cx.spawn(|workspace, mut cx| async move {
1948            let open_paths_task_result = workspace
1949                .update(&mut cx, |workspace, cx| {
1950                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
1951                })
1952                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1953                .await;
1954            anyhow::ensure!(
1955                open_paths_task_result.len() == 1,
1956                "open abs path {abs_path:?} task returned incorrect number of results"
1957            );
1958            match open_paths_task_result
1959                .into_iter()
1960                .next()
1961                .expect("ensured single task result")
1962            {
1963                Some(open_result) => {
1964                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1965                }
1966                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1967            }
1968        })
1969    }
1970
1971    pub fn split_abs_path(
1972        &mut self,
1973        abs_path: PathBuf,
1974        visible: bool,
1975        cx: &mut ViewContext<Self>,
1976    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1977        let project_path_task =
1978            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
1979        cx.spawn(|this, mut cx| async move {
1980            let (_, path) = project_path_task.await?;
1981            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
1982                .await
1983        })
1984    }
1985
1986    pub fn open_path(
1987        &mut self,
1988        path: impl Into<ProjectPath>,
1989        pane: Option<WeakViewHandle<Pane>>,
1990        focus_item: bool,
1991        cx: &mut ViewContext<Self>,
1992    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1993        let pane = pane.unwrap_or_else(|| {
1994            self.last_active_center_pane.clone().unwrap_or_else(|| {
1995                self.panes
1996                    .first()
1997                    .expect("There must be an active pane")
1998                    .downgrade()
1999            })
2000        });
2001
2002        let task = self.load_path(path.into(), cx);
2003        cx.spawn(|_, mut cx| async move {
2004            let (project_entry_id, build_item) = task.await?;
2005            pane.update(&mut cx, |pane, cx| {
2006                pane.open_item(project_entry_id, focus_item, cx, build_item)
2007            })
2008        })
2009    }
2010
2011    pub fn split_path(
2012        &mut self,
2013        path: impl Into<ProjectPath>,
2014        cx: &mut ViewContext<Self>,
2015    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2016        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2017            self.panes
2018                .first()
2019                .expect("There must be an active pane")
2020                .downgrade()
2021        });
2022
2023        if let Member::Pane(center_pane) = &self.center.root {
2024            if center_pane.read(cx).items_len() == 0 {
2025                return self.open_path(path, Some(pane), true, cx);
2026            }
2027        }
2028
2029        let task = self.load_path(path.into(), cx);
2030        cx.spawn(|this, mut cx| async move {
2031            let (project_entry_id, build_item) = task.await?;
2032            this.update(&mut cx, move |this, cx| -> Option<_> {
2033                let pane = pane.upgrade(cx)?;
2034                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2035                new_pane.update(cx, |new_pane, cx| {
2036                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
2037                })
2038            })
2039            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2040        })
2041    }
2042
2043    pub(crate) fn load_path(
2044        &mut self,
2045        path: ProjectPath,
2046        cx: &mut ViewContext<Self>,
2047    ) -> Task<
2048        Result<(
2049            ProjectEntryId,
2050            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
2051        )>,
2052    > {
2053        let project = self.project().clone();
2054        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
2055        cx.spawn(|_, mut cx| async move {
2056            let (project_entry_id, project_item) = project_item.await?;
2057            let build_item = cx.update(|cx| {
2058                cx.default_global::<ProjectItemBuilders>()
2059                    .get(&project_item.model_type())
2060                    .ok_or_else(|| anyhow!("no item builder for project item"))
2061                    .cloned()
2062            })?;
2063            let build_item =
2064                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
2065            Ok((project_entry_id, build_item))
2066        })
2067    }
2068
2069    pub fn open_project_item<T>(
2070        &mut self,
2071        project_item: ModelHandle<T::Item>,
2072        cx: &mut ViewContext<Self>,
2073    ) -> ViewHandle<T>
2074    where
2075        T: ProjectItem,
2076    {
2077        use project::Item as _;
2078
2079        let entry_id = project_item.read(cx).entry_id(cx);
2080        if let Some(item) = entry_id
2081            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2082            .and_then(|item| item.downcast())
2083        {
2084            self.activate_item(&item, cx);
2085            return item;
2086        }
2087
2088        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2089        self.add_item(Box::new(item.clone()), cx);
2090        item
2091    }
2092
2093    pub fn split_project_item<T>(
2094        &mut self,
2095        project_item: ModelHandle<T::Item>,
2096        cx: &mut ViewContext<Self>,
2097    ) -> ViewHandle<T>
2098    where
2099        T: ProjectItem,
2100    {
2101        use project::Item as _;
2102
2103        let entry_id = project_item.read(cx).entry_id(cx);
2104        if let Some(item) = entry_id
2105            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2106            .and_then(|item| item.downcast())
2107        {
2108            self.activate_item(&item, cx);
2109            return item;
2110        }
2111
2112        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2113        self.split_item(Box::new(item.clone()), cx);
2114        item
2115    }
2116
2117    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2118        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2119            self.active_pane.update(cx, |pane, cx| {
2120                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2121            });
2122        }
2123    }
2124
2125    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
2126        let result = self.panes.iter().find_map(|pane| {
2127            pane.read(cx)
2128                .index_for_item(item)
2129                .map(|ix| (pane.clone(), ix))
2130        });
2131        if let Some((pane, ix)) = result {
2132            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2133            true
2134        } else {
2135            false
2136        }
2137    }
2138
2139    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2140        let panes = self.center.panes();
2141        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2142            cx.focus(&pane);
2143        } else {
2144            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2145        }
2146    }
2147
2148    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
2149        let panes = self.center.panes();
2150        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2151            let next_ix = (ix + 1) % panes.len();
2152            let next_pane = panes[next_ix].clone();
2153            cx.focus(&next_pane);
2154        }
2155    }
2156
2157    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2158        let panes = self.center.panes();
2159        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2160            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2161            let prev_pane = panes[prev_ix].clone();
2162            cx.focus(&prev_pane);
2163        }
2164    }
2165
2166    pub fn activate_pane_in_direction(
2167        &mut self,
2168        direction: SplitDirection,
2169        cx: &mut ViewContext<Self>,
2170    ) {
2171        let bounding_box = match self.center.bounding_box_for_pane(&self.active_pane) {
2172            Some(coordinates) => coordinates,
2173            None => {
2174                return;
2175            }
2176        };
2177        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2178        let center = match cursor {
2179            Some(cursor) if bounding_box.contains_point(cursor) => cursor,
2180            _ => bounding_box.center(),
2181        };
2182
2183        let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
2184
2185        let target = match direction {
2186            SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
2187            SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
2188            SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
2189            SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
2190        };
2191
2192        if let Some(pane) = self.center.pane_at_pixel_position(target) {
2193            cx.focus(pane);
2194        }
2195    }
2196
2197    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2198        if self.active_pane != pane {
2199            self.active_pane = pane.clone();
2200            self.status_bar.update(cx, |status_bar, cx| {
2201                status_bar.set_active_pane(&self.active_pane, cx);
2202            });
2203            self.active_item_path_changed(cx);
2204            self.last_active_center_pane = Some(pane.downgrade());
2205        }
2206
2207        self.dismiss_zoomed_items_to_reveal(None, cx);
2208        if pane.read(cx).is_zoomed() {
2209            self.zoomed = Some(pane.downgrade().into_any());
2210        } else {
2211            self.zoomed = None;
2212        }
2213        self.zoomed_position = None;
2214        self.update_active_view_for_followers(cx);
2215
2216        cx.notify();
2217    }
2218
2219    fn handle_pane_event(
2220        &mut self,
2221        pane: ViewHandle<Pane>,
2222        event: &pane::Event,
2223        cx: &mut ViewContext<Self>,
2224    ) {
2225        match event {
2226            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2227            pane::Event::Split(direction) => {
2228                self.split_and_clone(pane, *direction, cx);
2229            }
2230            pane::Event::Remove => self.remove_pane(pane, cx),
2231            pane::Event::ActivateItem { local } => {
2232                if *local {
2233                    self.unfollow(&pane, cx);
2234                }
2235                if &pane == self.active_pane() {
2236                    self.active_item_path_changed(cx);
2237                }
2238            }
2239            pane::Event::ChangeItemTitle => {
2240                if pane == self.active_pane {
2241                    self.active_item_path_changed(cx);
2242                }
2243                self.update_window_edited(cx);
2244            }
2245            pane::Event::RemoveItem { item_id } => {
2246                self.update_window_edited(cx);
2247                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2248                    if entry.get().id() == pane.id() {
2249                        entry.remove();
2250                    }
2251                }
2252            }
2253            pane::Event::Focus => {
2254                self.handle_pane_focused(pane.clone(), cx);
2255            }
2256            pane::Event::ZoomIn => {
2257                if pane == self.active_pane {
2258                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2259                    if pane.read(cx).has_focus() {
2260                        self.zoomed = Some(pane.downgrade().into_any());
2261                        self.zoomed_position = None;
2262                    }
2263                    cx.notify();
2264                }
2265            }
2266            pane::Event::ZoomOut => {
2267                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2268                if self.zoomed_position.is_none() {
2269                    self.zoomed = None;
2270                }
2271                cx.notify();
2272            }
2273        }
2274
2275        self.serialize_workspace(cx);
2276    }
2277
2278    pub fn split_pane(
2279        &mut self,
2280        pane_to_split: ViewHandle<Pane>,
2281        split_direction: SplitDirection,
2282        cx: &mut ViewContext<Self>,
2283    ) -> ViewHandle<Pane> {
2284        let new_pane = self.add_pane(cx);
2285        self.center
2286            .split(&pane_to_split, &new_pane, split_direction)
2287            .unwrap();
2288        cx.notify();
2289        new_pane
2290    }
2291
2292    pub fn split_and_clone(
2293        &mut self,
2294        pane: ViewHandle<Pane>,
2295        direction: SplitDirection,
2296        cx: &mut ViewContext<Self>,
2297    ) -> Option<ViewHandle<Pane>> {
2298        let item = pane.read(cx).active_item()?;
2299        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2300            let new_pane = self.add_pane(cx);
2301            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2302            self.center.split(&pane, &new_pane, direction).unwrap();
2303            Some(new_pane)
2304        } else {
2305            None
2306        };
2307        cx.notify();
2308        maybe_pane_handle
2309    }
2310
2311    pub fn split_pane_with_item(
2312        &mut self,
2313        pane_to_split: WeakViewHandle<Pane>,
2314        split_direction: SplitDirection,
2315        from: WeakViewHandle<Pane>,
2316        item_id_to_move: usize,
2317        cx: &mut ViewContext<Self>,
2318    ) {
2319        let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
2320            return;
2321        };
2322        let Some(from) = from.upgrade(cx) else {
2323            return;
2324        };
2325
2326        let new_pane = self.add_pane(cx);
2327        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2328        self.center
2329            .split(&pane_to_split, &new_pane, split_direction)
2330            .unwrap();
2331        cx.notify();
2332    }
2333
2334    pub fn split_pane_with_project_entry(
2335        &mut self,
2336        pane_to_split: WeakViewHandle<Pane>,
2337        split_direction: SplitDirection,
2338        project_entry: ProjectEntryId,
2339        cx: &mut ViewContext<Self>,
2340    ) -> Option<Task<Result<()>>> {
2341        let pane_to_split = pane_to_split.upgrade(cx)?;
2342        let new_pane = self.add_pane(cx);
2343        self.center
2344            .split(&pane_to_split, &new_pane, split_direction)
2345            .unwrap();
2346
2347        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2348        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2349        Some(cx.foreground().spawn(async move {
2350            task.await?;
2351            Ok(())
2352        }))
2353    }
2354
2355    pub fn move_item(
2356        &mut self,
2357        source: ViewHandle<Pane>,
2358        destination: ViewHandle<Pane>,
2359        item_id_to_move: usize,
2360        destination_index: usize,
2361        cx: &mut ViewContext<Self>,
2362    ) {
2363        let item_to_move = source
2364            .read(cx)
2365            .items()
2366            .enumerate()
2367            .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2368
2369        if item_to_move.is_none() {
2370            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2371            return;
2372        }
2373        let (item_ix, item_handle) = item_to_move.unwrap();
2374        let item_handle = item_handle.clone();
2375
2376        if source != destination {
2377            // Close item from previous pane
2378            source.update(cx, |source, cx| {
2379                source.remove_item(item_ix, false, cx);
2380            });
2381        }
2382
2383        // This automatically removes duplicate items in the pane
2384        destination.update(cx, |destination, cx| {
2385            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2386            cx.focus_self();
2387        });
2388    }
2389
2390    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2391        if self.center.remove(&pane).unwrap() {
2392            self.force_remove_pane(&pane, cx);
2393            self.unfollow(&pane, cx);
2394            self.last_leaders_by_pane.remove(&pane.downgrade());
2395            for removed_item in pane.read(cx).items() {
2396                self.panes_by_item.remove(&removed_item.id());
2397            }
2398
2399            cx.notify();
2400        } else {
2401            self.active_item_path_changed(cx);
2402        }
2403    }
2404
2405    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2406        &self.panes
2407    }
2408
2409    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2410        &self.active_pane
2411    }
2412
2413    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2414        if let Some(remote_id) = remote_id {
2415            self.remote_entity_subscription = Some(
2416                self.app_state
2417                    .client
2418                    .add_view_for_remote_entity(remote_id, cx),
2419            );
2420        } else {
2421            self.remote_entity_subscription.take();
2422        }
2423    }
2424
2425    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2426        self.leader_state.followers.remove(&peer_id);
2427        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2428            for state in states_by_pane.into_values() {
2429                for item in state.items_by_leader_view_id.into_values() {
2430                    item.set_leader_replica_id(None, cx);
2431                }
2432            }
2433        }
2434        cx.notify();
2435    }
2436
2437    pub fn toggle_follow(
2438        &mut self,
2439        leader_id: PeerId,
2440        cx: &mut ViewContext<Self>,
2441    ) -> Option<Task<Result<()>>> {
2442        let pane = self.active_pane().clone();
2443
2444        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2445            if leader_id == prev_leader_id {
2446                return None;
2447            }
2448        }
2449
2450        self.last_leaders_by_pane
2451            .insert(pane.downgrade(), leader_id);
2452        self.follower_states_by_leader
2453            .entry(leader_id)
2454            .or_default()
2455            .insert(pane.clone(), Default::default());
2456        cx.notify();
2457
2458        let project_id = self.project.read(cx).remote_id()?;
2459        let request = self.app_state.client.request(proto::Follow {
2460            project_id,
2461            leader_id: Some(leader_id),
2462        });
2463
2464        Some(cx.spawn(|this, mut cx| async move {
2465            let response = request.await?;
2466            this.update(&mut cx, |this, _| {
2467                let state = this
2468                    .follower_states_by_leader
2469                    .get_mut(&leader_id)
2470                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2471                    .ok_or_else(|| anyhow!("following interrupted"))?;
2472                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2473                    Some(ViewId::from_proto(active_view_id)?)
2474                } else {
2475                    None
2476                };
2477                Ok::<_, anyhow::Error>(())
2478            })??;
2479            Self::add_views_from_leader(
2480                this.clone(),
2481                leader_id,
2482                vec![pane],
2483                response.views,
2484                &mut cx,
2485            )
2486            .await?;
2487            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2488            Ok(())
2489        }))
2490    }
2491
2492    pub fn follow_next_collaborator(
2493        &mut self,
2494        _: &FollowNextCollaborator,
2495        cx: &mut ViewContext<Self>,
2496    ) -> Option<Task<Result<()>>> {
2497        let collaborators = self.project.read(cx).collaborators();
2498        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2499            let mut collaborators = collaborators.keys().copied();
2500            for peer_id in collaborators.by_ref() {
2501                if peer_id == leader_id {
2502                    break;
2503                }
2504            }
2505            collaborators.next()
2506        } else if let Some(last_leader_id) =
2507            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2508        {
2509            if collaborators.contains_key(last_leader_id) {
2510                Some(*last_leader_id)
2511            } else {
2512                None
2513            }
2514        } else {
2515            None
2516        };
2517
2518        next_leader_id
2519            .or_else(|| collaborators.keys().copied().next())
2520            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2521    }
2522
2523    pub fn unfollow(
2524        &mut self,
2525        pane: &ViewHandle<Pane>,
2526        cx: &mut ViewContext<Self>,
2527    ) -> Option<PeerId> {
2528        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2529            let leader_id = *leader_id;
2530            if let Some(state) = states_by_pane.remove(pane) {
2531                for (_, item) in state.items_by_leader_view_id {
2532                    item.set_leader_replica_id(None, cx);
2533                }
2534
2535                if states_by_pane.is_empty() {
2536                    self.follower_states_by_leader.remove(&leader_id);
2537                    if let Some(project_id) = self.project.read(cx).remote_id() {
2538                        self.app_state
2539                            .client
2540                            .send(proto::Unfollow {
2541                                project_id,
2542                                leader_id: Some(leader_id),
2543                            })
2544                            .log_err();
2545                    }
2546                }
2547
2548                cx.notify();
2549                return Some(leader_id);
2550            }
2551        }
2552        None
2553    }
2554
2555    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2556        self.follower_states_by_leader.contains_key(&peer_id)
2557    }
2558
2559    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2560        self.leader_state.followers.contains(&peer_id)
2561    }
2562
2563    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2564        // TODO: There should be a better system in place for this
2565        // (https://github.com/zed-industries/zed/issues/1290)
2566        let is_fullscreen = cx.window_is_fullscreen();
2567        let container_theme = if is_fullscreen {
2568            let mut container_theme = theme.titlebar.container;
2569            container_theme.padding.left = container_theme.padding.right;
2570            container_theme
2571        } else {
2572            theme.titlebar.container
2573        };
2574
2575        enum TitleBar {}
2576        MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
2577            Stack::new()
2578                .with_children(
2579                    self.titlebar_item
2580                        .as_ref()
2581                        .map(|item| ChildView::new(item, cx)),
2582                )
2583                .contained()
2584                .with_style(container_theme)
2585        })
2586        .on_click(MouseButton::Left, |event, _, cx| {
2587            if event.click_count == 2 {
2588                cx.zoom_window();
2589            }
2590        })
2591        .constrained()
2592        .with_height(theme.titlebar.height)
2593        .into_any_named("titlebar")
2594    }
2595
2596    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2597        let active_entry = self.active_project_path(cx);
2598        self.project
2599            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2600        self.update_window_title(cx);
2601    }
2602
2603    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2604        let project = self.project().read(cx);
2605        let mut title = String::new();
2606
2607        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2608            let filename = path
2609                .path
2610                .file_name()
2611                .map(|s| s.to_string_lossy())
2612                .or_else(|| {
2613                    Some(Cow::Borrowed(
2614                        project
2615                            .worktree_for_id(path.worktree_id, cx)?
2616                            .read(cx)
2617                            .root_name(),
2618                    ))
2619                });
2620
2621            if let Some(filename) = filename {
2622                title.push_str(filename.as_ref());
2623                title.push_str(" β€” ");
2624            }
2625        }
2626
2627        for (i, name) in project.worktree_root_names(cx).enumerate() {
2628            if i > 0 {
2629                title.push_str(", ");
2630            }
2631            title.push_str(name);
2632        }
2633
2634        if title.is_empty() {
2635            title = "empty project".to_string();
2636        }
2637
2638        if project.is_remote() {
2639            title.push_str(" ↙");
2640        } else if project.is_shared() {
2641            title.push_str(" β†—");
2642        }
2643
2644        cx.set_window_title(&title);
2645    }
2646
2647    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2648        let is_edited = !self.project.read(cx).is_read_only()
2649            && self
2650                .items(cx)
2651                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2652        if is_edited != self.window_edited {
2653            self.window_edited = is_edited;
2654            cx.set_window_edited(self.window_edited)
2655        }
2656    }
2657
2658    fn render_disconnected_overlay(
2659        &self,
2660        cx: &mut ViewContext<Workspace>,
2661    ) -> Option<AnyElement<Workspace>> {
2662        if self.project.read(cx).is_read_only() {
2663            enum DisconnectedOverlay {}
2664            Some(
2665                MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2666                    let theme = &theme::current(cx);
2667                    Label::new(
2668                        "Your connection to the remote project has been lost.",
2669                        theme.workspace.disconnected_overlay.text.clone(),
2670                    )
2671                    .aligned()
2672                    .contained()
2673                    .with_style(theme.workspace.disconnected_overlay.container)
2674                })
2675                .with_cursor_style(CursorStyle::Arrow)
2676                .capture_all()
2677                .into_any_named("disconnected overlay"),
2678            )
2679        } else {
2680            None
2681        }
2682    }
2683
2684    fn render_notifications(
2685        &self,
2686        theme: &theme::Workspace,
2687        cx: &AppContext,
2688    ) -> Option<AnyElement<Workspace>> {
2689        if self.notifications.is_empty() {
2690            None
2691        } else {
2692            Some(
2693                Flex::column()
2694                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2695                        ChildView::new(notification.as_any(), cx)
2696                            .contained()
2697                            .with_style(theme.notification)
2698                    }))
2699                    .constrained()
2700                    .with_width(theme.notifications.width)
2701                    .contained()
2702                    .with_style(theme.notifications.container)
2703                    .aligned()
2704                    .bottom()
2705                    .right()
2706                    .into_any(),
2707            )
2708        }
2709    }
2710
2711    // RPC handlers
2712
2713    async fn handle_follow(
2714        this: WeakViewHandle<Self>,
2715        envelope: TypedEnvelope<proto::Follow>,
2716        _: Arc<Client>,
2717        mut cx: AsyncAppContext,
2718    ) -> Result<proto::FollowResponse> {
2719        this.update(&mut cx, |this, cx| {
2720            let client = &this.app_state.client;
2721            this.leader_state
2722                .followers
2723                .insert(envelope.original_sender_id()?);
2724
2725            let active_view_id = this.active_item(cx).and_then(|i| {
2726                Some(
2727                    i.to_followable_item_handle(cx)?
2728                        .remote_id(client, cx)?
2729                        .to_proto(),
2730                )
2731            });
2732
2733            cx.notify();
2734
2735            Ok(proto::FollowResponse {
2736                active_view_id,
2737                views: this
2738                    .panes()
2739                    .iter()
2740                    .flat_map(|pane| {
2741                        let leader_id = this.leader_for_pane(pane);
2742                        pane.read(cx).items().filter_map({
2743                            let cx = &cx;
2744                            move |item| {
2745                                let item = item.to_followable_item_handle(cx)?;
2746                                let id = item.remote_id(client, cx)?.to_proto();
2747                                let variant = item.to_state_proto(cx)?;
2748                                Some(proto::View {
2749                                    id: Some(id),
2750                                    leader_id,
2751                                    variant: Some(variant),
2752                                })
2753                            }
2754                        })
2755                    })
2756                    .collect(),
2757            })
2758        })?
2759    }
2760
2761    async fn handle_unfollow(
2762        this: WeakViewHandle<Self>,
2763        envelope: TypedEnvelope<proto::Unfollow>,
2764        _: Arc<Client>,
2765        mut cx: AsyncAppContext,
2766    ) -> Result<()> {
2767        this.update(&mut cx, |this, cx| {
2768            this.leader_state
2769                .followers
2770                .remove(&envelope.original_sender_id()?);
2771            cx.notify();
2772            Ok(())
2773        })?
2774    }
2775
2776    async fn handle_update_followers(
2777        this: WeakViewHandle<Self>,
2778        envelope: TypedEnvelope<proto::UpdateFollowers>,
2779        _: Arc<Client>,
2780        cx: AsyncAppContext,
2781    ) -> Result<()> {
2782        let leader_id = envelope.original_sender_id()?;
2783        this.read_with(&cx, |this, _| {
2784            this.leader_updates_tx
2785                .unbounded_send((leader_id, envelope.payload))
2786        })??;
2787        Ok(())
2788    }
2789
2790    async fn process_leader_update(
2791        this: &WeakViewHandle<Self>,
2792        leader_id: PeerId,
2793        update: proto::UpdateFollowers,
2794        cx: &mut AsyncAppContext,
2795    ) -> Result<()> {
2796        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2797            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2798                this.update(cx, |this, _| {
2799                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2800                        for state in state.values_mut() {
2801                            state.active_view_id =
2802                                if let Some(active_view_id) = update_active_view.id.clone() {
2803                                    Some(ViewId::from_proto(active_view_id)?)
2804                                } else {
2805                                    None
2806                                };
2807                        }
2808                    }
2809                    anyhow::Ok(())
2810                })??;
2811            }
2812            proto::update_followers::Variant::UpdateView(update_view) => {
2813                let variant = update_view
2814                    .variant
2815                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2816                let id = update_view
2817                    .id
2818                    .ok_or_else(|| anyhow!("missing update view id"))?;
2819                let mut tasks = Vec::new();
2820                this.update(cx, |this, cx| {
2821                    let project = this.project.clone();
2822                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2823                        for state in state.values_mut() {
2824                            let view_id = ViewId::from_proto(id.clone())?;
2825                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2826                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2827                            }
2828                        }
2829                    }
2830                    anyhow::Ok(())
2831                })??;
2832                try_join_all(tasks).await.log_err();
2833            }
2834            proto::update_followers::Variant::CreateView(view) => {
2835                let panes = this.read_with(cx, |this, _| {
2836                    this.follower_states_by_leader
2837                        .get(&leader_id)
2838                        .into_iter()
2839                        .flat_map(|states_by_pane| states_by_pane.keys())
2840                        .cloned()
2841                        .collect()
2842                })?;
2843                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2844            }
2845        }
2846        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2847        Ok(())
2848    }
2849
2850    async fn add_views_from_leader(
2851        this: WeakViewHandle<Self>,
2852        leader_id: PeerId,
2853        panes: Vec<ViewHandle<Pane>>,
2854        views: Vec<proto::View>,
2855        cx: &mut AsyncAppContext,
2856    ) -> Result<()> {
2857        let this = this
2858            .upgrade(cx)
2859            .ok_or_else(|| anyhow!("workspace dropped"))?;
2860        let project = this
2861            .read_with(cx, |this, _| this.project.clone())
2862            .ok_or_else(|| anyhow!("window dropped"))?;
2863
2864        let replica_id = project
2865            .read_with(cx, |project, _| {
2866                project
2867                    .collaborators()
2868                    .get(&leader_id)
2869                    .map(|c| c.replica_id)
2870            })
2871            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2872
2873        let item_builders = cx.update(|cx| {
2874            cx.default_global::<FollowableItemBuilders>()
2875                .values()
2876                .map(|b| b.0)
2877                .collect::<Vec<_>>()
2878        });
2879
2880        let mut item_tasks_by_pane = HashMap::default();
2881        for pane in panes {
2882            let mut item_tasks = Vec::new();
2883            let mut leader_view_ids = Vec::new();
2884            for view in &views {
2885                let Some(id) = &view.id else { continue };
2886                let id = ViewId::from_proto(id.clone())?;
2887                let mut variant = view.variant.clone();
2888                if variant.is_none() {
2889                    Err(anyhow!("missing view variant"))?;
2890                }
2891                for build_item in &item_builders {
2892                    let task = cx
2893                        .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
2894                    if let Some(task) = task {
2895                        item_tasks.push(task);
2896                        leader_view_ids.push(id);
2897                        break;
2898                    } else {
2899                        assert!(variant.is_some());
2900                    }
2901                }
2902            }
2903
2904            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2905        }
2906
2907        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2908            let items = futures::future::try_join_all(item_tasks).await?;
2909            this.update(cx, |this, cx| {
2910                let state = this
2911                    .follower_states_by_leader
2912                    .get_mut(&leader_id)?
2913                    .get_mut(&pane)?;
2914
2915                for (id, item) in leader_view_ids.into_iter().zip(items) {
2916                    item.set_leader_replica_id(Some(replica_id), cx);
2917                    state.items_by_leader_view_id.insert(id, item);
2918                }
2919
2920                Some(())
2921            });
2922        }
2923        Ok(())
2924    }
2925
2926    fn update_active_view_for_followers(&self, cx: &AppContext) {
2927        if self.active_pane.read(cx).has_focus() {
2928            self.update_followers(
2929                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2930                    id: self.active_item(cx).and_then(|item| {
2931                        item.to_followable_item_handle(cx)?
2932                            .remote_id(&self.app_state.client, cx)
2933                            .map(|id| id.to_proto())
2934                    }),
2935                    leader_id: self.leader_for_pane(&self.active_pane),
2936                }),
2937                cx,
2938            );
2939        } else {
2940            self.update_followers(
2941                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2942                    id: None,
2943                    leader_id: None,
2944                }),
2945                cx,
2946            );
2947        }
2948    }
2949
2950    fn update_followers(
2951        &self,
2952        update: proto::update_followers::Variant,
2953        cx: &AppContext,
2954    ) -> Option<()> {
2955        let project_id = self.project.read(cx).remote_id()?;
2956        if !self.leader_state.followers.is_empty() {
2957            self.app_state
2958                .client
2959                .send(proto::UpdateFollowers {
2960                    project_id,
2961                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2962                    variant: Some(update),
2963                })
2964                .log_err();
2965        }
2966        None
2967    }
2968
2969    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2970        self.follower_states_by_leader
2971            .iter()
2972            .find_map(|(leader_id, state)| {
2973                if state.contains_key(pane) {
2974                    Some(*leader_id)
2975                } else {
2976                    None
2977                }
2978            })
2979    }
2980
2981    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2982        cx.notify();
2983
2984        let call = self.active_call()?;
2985        let room = call.read(cx).room()?.read(cx);
2986        let participant = room.remote_participant_for_peer_id(leader_id)?;
2987        let mut items_to_activate = Vec::new();
2988        match participant.location {
2989            call::ParticipantLocation::SharedProject { project_id } => {
2990                if Some(project_id) == self.project.read(cx).remote_id() {
2991                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2992                        if let Some(item) = state
2993                            .active_view_id
2994                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2995                        {
2996                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2997                        } else if let Some(shared_screen) =
2998                            self.shared_screen_for_peer(leader_id, pane, cx)
2999                        {
3000                            items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3001                        }
3002                    }
3003                }
3004            }
3005            call::ParticipantLocation::UnsharedProject => {}
3006            call::ParticipantLocation::External => {
3007                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
3008                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3009                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3010                    }
3011                }
3012            }
3013        }
3014
3015        for (pane, item) in items_to_activate {
3016            let pane_was_focused = pane.read(cx).has_focus();
3017            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3018                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3019            } else {
3020                pane.update(cx, |pane, cx| {
3021                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3022                });
3023            }
3024
3025            if pane_was_focused {
3026                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3027            }
3028        }
3029
3030        None
3031    }
3032
3033    fn shared_screen_for_peer(
3034        &self,
3035        peer_id: PeerId,
3036        pane: &ViewHandle<Pane>,
3037        cx: &mut ViewContext<Self>,
3038    ) -> Option<ViewHandle<SharedScreen>> {
3039        let call = self.active_call()?;
3040        let room = call.read(cx).room()?.read(cx);
3041        let participant = room.remote_participant_for_peer_id(peer_id)?;
3042        let track = participant.video_tracks.values().next()?.clone();
3043        let user = participant.user.clone();
3044
3045        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3046            if item.read(cx).peer_id == peer_id {
3047                return Some(item);
3048            }
3049        }
3050
3051        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3052    }
3053
3054    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3055        if active {
3056            cx.background()
3057                .spawn(persistence::DB.update_timestamp(self.database_id()))
3058                .detach();
3059        } else {
3060            for pane in &self.panes {
3061                pane.update(cx, |pane, cx| {
3062                    if let Some(item) = pane.active_item() {
3063                        item.workspace_deactivated(cx);
3064                    }
3065                    if matches!(
3066                        settings::get::<WorkspaceSettings>(cx).autosave,
3067                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3068                    ) {
3069                        for item in pane.items() {
3070                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3071                                .detach_and_log_err(cx);
3072                        }
3073                    }
3074                });
3075            }
3076        }
3077    }
3078
3079    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3080        self.active_call.as_ref().map(|(call, _)| call)
3081    }
3082
3083    fn on_active_call_event(
3084        &mut self,
3085        _: ModelHandle<ActiveCall>,
3086        event: &call::room::Event,
3087        cx: &mut ViewContext<Self>,
3088    ) {
3089        match event {
3090            call::room::Event::ParticipantLocationChanged { participant_id }
3091            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3092                self.leader_updated(*participant_id, cx);
3093            }
3094            _ => {}
3095        }
3096    }
3097
3098    pub fn database_id(&self) -> WorkspaceId {
3099        self.database_id
3100    }
3101
3102    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3103        let project = self.project().read(cx);
3104
3105        if project.is_local() {
3106            Some(
3107                project
3108                    .visible_worktrees(cx)
3109                    .map(|worktree| worktree.read(cx).abs_path())
3110                    .collect::<Vec<_>>()
3111                    .into(),
3112            )
3113        } else {
3114            None
3115        }
3116    }
3117
3118    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3119        match member {
3120            Member::Axis(PaneAxis { members, .. }) => {
3121                for child in members.iter() {
3122                    self.remove_panes(child.clone(), cx)
3123                }
3124            }
3125            Member::Pane(pane) => {
3126                self.force_remove_pane(&pane, cx);
3127            }
3128        }
3129    }
3130
3131    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3132        self.panes.retain(|p| p != pane);
3133        cx.focus(self.panes.last().unwrap());
3134        if self.last_active_center_pane == Some(pane.downgrade()) {
3135            self.last_active_center_pane = None;
3136        }
3137        cx.notify();
3138    }
3139
3140    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3141        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3142            cx.background().timer(Duration::from_millis(100)).await;
3143            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3144                .ok();
3145        }));
3146    }
3147
3148    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3149        fn serialize_pane_handle(
3150            pane_handle: &ViewHandle<Pane>,
3151            cx: &AppContext,
3152        ) -> SerializedPane {
3153            let (items, active) = {
3154                let pane = pane_handle.read(cx);
3155                let active_item_id = pane.active_item().map(|item| item.id());
3156                (
3157                    pane.items()
3158                        .filter_map(|item_handle| {
3159                            Some(SerializedItem {
3160                                kind: Arc::from(item_handle.serialized_item_kind()?),
3161                                item_id: item_handle.id(),
3162                                active: Some(item_handle.id()) == active_item_id,
3163                            })
3164                        })
3165                        .collect::<Vec<_>>(),
3166                    pane.has_focus(),
3167                )
3168            };
3169
3170            SerializedPane::new(items, active)
3171        }
3172
3173        fn build_serialized_pane_group(
3174            pane_group: &Member,
3175            cx: &AppContext,
3176        ) -> SerializedPaneGroup {
3177            match pane_group {
3178                Member::Axis(PaneAxis {
3179                    axis,
3180                    members,
3181                    flexes,
3182                    bounding_boxes: _,
3183                }) => SerializedPaneGroup::Group {
3184                    axis: *axis,
3185                    children: members
3186                        .iter()
3187                        .map(|member| build_serialized_pane_group(member, cx))
3188                        .collect::<Vec<_>>(),
3189                    flexes: Some(flexes.borrow().clone()),
3190                },
3191                Member::Pane(pane_handle) => {
3192                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3193                }
3194            }
3195        }
3196
3197        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3198            let left_dock = this.left_dock.read(cx);
3199            let left_visible = left_dock.is_open();
3200            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3201                Some(
3202                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3203                        .to_string(),
3204                )
3205            });
3206            let left_dock_zoom = left_dock
3207                .visible_panel()
3208                .map(|panel| panel.is_zoomed(cx))
3209                .unwrap_or(false);
3210
3211            let right_dock = this.right_dock.read(cx);
3212            let right_visible = right_dock.is_open();
3213            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3214                Some(
3215                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3216                        .to_string(),
3217                )
3218            });
3219            let right_dock_zoom = right_dock
3220                .visible_panel()
3221                .map(|panel| panel.is_zoomed(cx))
3222                .unwrap_or(false);
3223
3224            let bottom_dock = this.bottom_dock.read(cx);
3225            let bottom_visible = bottom_dock.is_open();
3226            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3227                Some(
3228                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3229                        .to_string(),
3230                )
3231            });
3232            let bottom_dock_zoom = bottom_dock
3233                .visible_panel()
3234                .map(|panel| panel.is_zoomed(cx))
3235                .unwrap_or(false);
3236
3237            DockStructure {
3238                left: DockData {
3239                    visible: left_visible,
3240                    active_panel: left_active_panel,
3241                    zoom: left_dock_zoom,
3242                },
3243                right: DockData {
3244                    visible: right_visible,
3245                    active_panel: right_active_panel,
3246                    zoom: right_dock_zoom,
3247                },
3248                bottom: DockData {
3249                    visible: bottom_visible,
3250                    active_panel: bottom_active_panel,
3251                    zoom: bottom_dock_zoom,
3252                },
3253            }
3254        }
3255
3256        if let Some(location) = self.location(cx) {
3257            // Load bearing special case:
3258            //  - with_local_workspace() relies on this to not have other stuff open
3259            //    when you open your log
3260            if !location.paths().is_empty() {
3261                let center_group = build_serialized_pane_group(&self.center.root, cx);
3262                let docks = build_serialized_docks(self, cx);
3263
3264                let serialized_workspace = SerializedWorkspace {
3265                    id: self.database_id,
3266                    location,
3267                    center_group,
3268                    bounds: Default::default(),
3269                    display: Default::default(),
3270                    docks,
3271                };
3272
3273                cx.background()
3274                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3275                    .detach();
3276            }
3277        }
3278    }
3279
3280    pub(crate) fn load_workspace(
3281        workspace: WeakViewHandle<Workspace>,
3282        serialized_workspace: SerializedWorkspace,
3283        paths_to_open: Vec<Option<ProjectPath>>,
3284        cx: &mut AppContext,
3285    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
3286        cx.spawn(|mut cx| async move {
3287            let result = async_iife! {{
3288                let (project, old_center_pane) =
3289                workspace.read_with(&cx, |workspace, _| {
3290                    (
3291                        workspace.project().clone(),
3292                        workspace.last_active_center_pane.clone(),
3293                    )
3294                })?;
3295
3296                let mut center_items = None;
3297                let mut center_group = None;
3298                // Traverse the splits tree and add to things
3299                if let Some((group, active_pane, items)) = serialized_workspace
3300                        .center_group
3301                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3302                        .await {
3303                    center_items = Some(items);
3304                    center_group = Some((group, active_pane))
3305                }
3306
3307                let resulting_list = cx.read(|cx| {
3308                    let mut opened_items = center_items
3309                        .unwrap_or_default()
3310                        .into_iter()
3311                        .filter_map(|item| {
3312                            let item = item?;
3313                            let project_path = item.project_path(cx)?;
3314                            Some((project_path, item))
3315                        })
3316                        .collect::<HashMap<_, _>>();
3317
3318                    paths_to_open
3319                        .into_iter()
3320                        .map(|path_to_open| {
3321                            path_to_open.map(|path_to_open| {
3322                                Ok(opened_items.remove(&path_to_open))
3323                            })
3324                            .transpose()
3325                            .map(|item| item.flatten())
3326                            .transpose()
3327                        })
3328                        .collect::<Vec<_>>()
3329                });
3330
3331                // Remove old panes from workspace panes list
3332                workspace.update(&mut cx, |workspace, cx| {
3333                    if let Some((center_group, active_pane)) = center_group {
3334                        workspace.remove_panes(workspace.center.root.clone(), cx);
3335
3336                        // Swap workspace center group
3337                        workspace.center = PaneGroup::with_root(center_group);
3338
3339                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3340                        cx.focus_self();
3341
3342                        if let Some(active_pane) = active_pane {
3343                            cx.focus(&active_pane);
3344                        } else {
3345                            cx.focus(workspace.panes.last().unwrap());
3346                        }
3347                    } else {
3348                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3349                        if let Some(old_center_handle) = old_center_handle {
3350                            cx.focus(&old_center_handle)
3351                        } else {
3352                            cx.focus_self()
3353                        }
3354                    }
3355
3356                    let docks = serialized_workspace.docks;
3357                    workspace.left_dock.update(cx, |dock, cx| {
3358                        dock.set_open(docks.left.visible, cx);
3359                        if let Some(active_panel) = docks.left.active_panel {
3360                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3361                                dock.activate_panel(ix, cx);
3362                            }
3363                        }
3364                                dock.active_panel()
3365                                    .map(|panel| {
3366                                        panel.set_zoomed(docks.left.zoom, cx)
3367                                    });
3368                                if docks.left.visible && docks.left.zoom {
3369                                    cx.focus_self()
3370                                }
3371                    });
3372                    // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3373                    workspace.right_dock.update(cx, |dock, cx| {
3374                        dock.set_open(docks.right.visible, cx);
3375                        if let Some(active_panel) = docks.right.active_panel {
3376                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3377                                dock.activate_panel(ix, cx);
3378
3379                            }
3380                        }
3381                                dock.active_panel()
3382                                    .map(|panel| {
3383                                        panel.set_zoomed(docks.right.zoom, cx)
3384                                    });
3385
3386                                if docks.right.visible && docks.right.zoom {
3387                                    cx.focus_self()
3388                                }
3389                    });
3390                    workspace.bottom_dock.update(cx, |dock, cx| {
3391                        dock.set_open(docks.bottom.visible, cx);
3392                        if let Some(active_panel) = docks.bottom.active_panel {
3393                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3394                                dock.activate_panel(ix, cx);
3395                            }
3396                        }
3397
3398                        dock.active_panel()
3399                            .map(|panel| {
3400                                panel.set_zoomed(docks.bottom.zoom, cx)
3401                            });
3402
3403                        if docks.bottom.visible && docks.bottom.zoom {
3404                            cx.focus_self()
3405                        }
3406                    });
3407
3408
3409                    cx.notify();
3410                })?;
3411
3412                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3413                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3414
3415                Ok::<_, anyhow::Error>(resulting_list)
3416            }};
3417
3418            result.await.unwrap_or_default()
3419        })
3420    }
3421
3422    #[cfg(any(test, feature = "test-support"))]
3423    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3424        let client = project.read(cx).client();
3425        let user_store = project.read(cx).user_store();
3426
3427        let channel_store =
3428            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
3429        let app_state = Arc::new(AppState {
3430            languages: project.read(cx).languages().clone(),
3431            client,
3432            user_store,
3433            channel_store,
3434            fs: project.read(cx).fs().clone(),
3435            build_window_options: |_, _, _| Default::default(),
3436            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3437            background_actions: || &[],
3438        });
3439        Self::new(0, project, app_state, cx)
3440    }
3441
3442    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3443        let dock = match position {
3444            DockPosition::Left => &self.left_dock,
3445            DockPosition::Right => &self.right_dock,
3446            DockPosition::Bottom => &self.bottom_dock,
3447        };
3448        let active_panel = dock.read(cx).visible_panel()?;
3449        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3450            dock.read(cx).render_placeholder(cx)
3451        } else {
3452            ChildView::new(dock, cx).into_any()
3453        };
3454
3455        Some(
3456            element
3457                .constrained()
3458                .dynamically(move |constraint, _, cx| match position {
3459                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3460                        Vector2F::new(20., constraint.min.y()),
3461                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3462                    ),
3463                    DockPosition::Bottom => SizeConstraint::new(
3464                        Vector2F::new(constraint.min.x(), 20.),
3465                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3466                    ),
3467                })
3468                .into_any(),
3469        )
3470    }
3471}
3472
3473fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3474    ZED_WINDOW_POSITION
3475        .zip(*ZED_WINDOW_SIZE)
3476        .map(|(position, size)| {
3477            WindowBounds::Fixed(RectF::new(
3478                cx.platform().screens()[0].bounds().origin() + position,
3479                size,
3480            ))
3481        })
3482}
3483
3484async fn open_items(
3485    serialized_workspace: Option<SerializedWorkspace>,
3486    workspace: &WeakViewHandle<Workspace>,
3487    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3488    app_state: Arc<AppState>,
3489    mut cx: AsyncAppContext,
3490) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3491    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3492
3493    if let Some(serialized_workspace) = serialized_workspace {
3494        let workspace = workspace.clone();
3495        let restored_items = cx
3496            .update(|cx| {
3497                Workspace::load_workspace(
3498                    workspace,
3499                    serialized_workspace,
3500                    project_paths_to_open
3501                        .iter()
3502                        .map(|(_, project_path)| project_path)
3503                        .cloned()
3504                        .collect(),
3505                    cx,
3506                )
3507            })
3508            .await;
3509
3510        let restored_project_paths = cx.read(|cx| {
3511            restored_items
3512                .iter()
3513                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3514                .collect::<HashSet<_>>()
3515        });
3516
3517        opened_items = restored_items;
3518        project_paths_to_open
3519            .iter_mut()
3520            .for_each(|(_, project_path)| {
3521                if let Some(project_path_to_open) = project_path {
3522                    if restored_project_paths.contains(project_path_to_open) {
3523                        *project_path = None;
3524                    }
3525                }
3526            });
3527    } else {
3528        for _ in 0..project_paths_to_open.len() {
3529            opened_items.push(None);
3530        }
3531    }
3532    assert!(opened_items.len() == project_paths_to_open.len());
3533
3534    let tasks =
3535        project_paths_to_open
3536            .into_iter()
3537            .enumerate()
3538            .map(|(i, (abs_path, project_path))| {
3539                let workspace = workspace.clone();
3540                cx.spawn(|mut cx| {
3541                    let fs = app_state.fs.clone();
3542                    async move {
3543                        let file_project_path = project_path?;
3544                        if fs.is_file(&abs_path).await {
3545                            Some((
3546                                i,
3547                                workspace
3548                                    .update(&mut cx, |workspace, cx| {
3549                                        workspace.open_path(file_project_path, None, true, cx)
3550                                    })
3551                                    .log_err()?
3552                                    .await,
3553                            ))
3554                        } else {
3555                            None
3556                        }
3557                    }
3558                })
3559            });
3560
3561    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3562        .await
3563        .into_iter()
3564    {
3565        if let Some((i, path_open_result)) = maybe_opened_path {
3566            opened_items[i] = Some(path_open_result);
3567        }
3568    }
3569
3570    opened_items
3571}
3572
3573fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3574    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3575    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3576    const MESSAGE_ID: usize = 2;
3577
3578    if workspace
3579        .read_with(cx, |workspace, cx| {
3580            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3581        })
3582        .unwrap_or(false)
3583    {
3584        return;
3585    }
3586
3587    if db::kvp::KEY_VALUE_STORE
3588        .read_kvp(NEW_DOCK_HINT_KEY)
3589        .ok()
3590        .flatten()
3591        .is_some()
3592    {
3593        if !workspace
3594            .read_with(cx, |workspace, cx| {
3595                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3596            })
3597            .unwrap_or(false)
3598        {
3599            cx.update(|cx| {
3600                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3601                    let entry = tracker
3602                        .entry(TypeId::of::<MessageNotification>())
3603                        .or_default();
3604                    if !entry.contains(&MESSAGE_ID) {
3605                        entry.push(MESSAGE_ID);
3606                    }
3607                });
3608            });
3609        }
3610
3611        return;
3612    }
3613
3614    cx.spawn(|_| async move {
3615        db::kvp::KEY_VALUE_STORE
3616            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3617            .await
3618            .ok();
3619    })
3620    .detach();
3621
3622    workspace
3623        .update(cx, |workspace, cx| {
3624            workspace.show_notification_once(2, cx, |cx| {
3625                cx.add_view(|_| {
3626                    MessageNotification::new_element(|text, _| {
3627                        Text::new(
3628                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3629                            text,
3630                        )
3631                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3632                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3633                                .theme
3634                                .editor
3635                                .document_highlight_read_background;
3636
3637                            cx.scene().push_quad(gpui::Quad {
3638                                bounds,
3639                                background: Some(code_span_background_color),
3640                                border: Default::default(),
3641                                corner_radii: (2.0).into(),
3642                            })
3643                        })
3644                        .into_any()
3645                    })
3646                    .with_click_message("Read more about the new panel system")
3647                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3648                })
3649            })
3650        })
3651        .ok();
3652}
3653
3654fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3655    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3656
3657    workspace
3658        .update(cx, |workspace, cx| {
3659            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3660                workspace.show_notification_once(0, cx, |cx| {
3661                    cx.add_view(|_| {
3662                        MessageNotification::new("Failed to load the database file.")
3663                            .with_click_message("Click to let us know about this error")
3664                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3665                    })
3666                });
3667            }
3668        })
3669        .log_err();
3670}
3671
3672impl Entity for Workspace {
3673    type Event = Event;
3674}
3675
3676impl View for Workspace {
3677    fn ui_name() -> &'static str {
3678        "Workspace"
3679    }
3680
3681    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3682        let theme = theme::current(cx).clone();
3683        Stack::new()
3684            .with_child(
3685                Flex::column()
3686                    .with_child(self.render_titlebar(&theme, cx))
3687                    .with_child(
3688                        Stack::new()
3689                            .with_child({
3690                                let project = self.project.clone();
3691                                Flex::row()
3692                                    .with_children(self.render_dock(DockPosition::Left, cx))
3693                                    .with_child(
3694                                        Flex::column()
3695                                            .with_child(
3696                                                FlexItem::new(
3697                                                    self.center.render(
3698                                                        &project,
3699                                                        &theme,
3700                                                        &self.follower_states_by_leader,
3701                                                        self.active_call(),
3702                                                        self.active_pane(),
3703                                                        self.zoomed
3704                                                            .as_ref()
3705                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3706                                                            .as_ref(),
3707                                                        &self.app_state,
3708                                                        cx,
3709                                                    ),
3710                                                )
3711                                                .flex(1., true),
3712                                            )
3713                                            .with_children(
3714                                                self.render_dock(DockPosition::Bottom, cx),
3715                                            )
3716                                            .flex(1., true),
3717                                    )
3718                                    .with_children(self.render_dock(DockPosition::Right, cx))
3719                            })
3720                            .with_child(Overlay::new(
3721                                Stack::new()
3722                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3723                                        enum ZoomBackground {}
3724                                        let zoomed = zoomed.upgrade(cx)?;
3725
3726                                        let mut foreground_style =
3727                                            theme.workspace.zoomed_pane_foreground;
3728                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3729                                            foreground_style =
3730                                                theme.workspace.zoomed_panel_foreground;
3731                                            let margin = foreground_style.margin.top;
3732                                            let border = foreground_style.border.top;
3733
3734                                            // Only include a margin and border on the opposite side.
3735                                            foreground_style.margin.top = 0.;
3736                                            foreground_style.margin.left = 0.;
3737                                            foreground_style.margin.bottom = 0.;
3738                                            foreground_style.margin.right = 0.;
3739                                            foreground_style.border.top = false;
3740                                            foreground_style.border.left = false;
3741                                            foreground_style.border.bottom = false;
3742                                            foreground_style.border.right = false;
3743                                            match zoomed_dock_position {
3744                                                DockPosition::Left => {
3745                                                    foreground_style.margin.right = margin;
3746                                                    foreground_style.border.right = border;
3747                                                }
3748                                                DockPosition::Right => {
3749                                                    foreground_style.margin.left = margin;
3750                                                    foreground_style.border.left = border;
3751                                                }
3752                                                DockPosition::Bottom => {
3753                                                    foreground_style.margin.top = margin;
3754                                                    foreground_style.border.top = border;
3755                                                }
3756                                            }
3757                                        }
3758
3759                                        Some(
3760                                            ChildView::new(&zoomed, cx)
3761                                                .contained()
3762                                                .with_style(foreground_style)
3763                                                .aligned()
3764                                                .contained()
3765                                                .with_style(theme.workspace.zoomed_background)
3766                                                .mouse::<ZoomBackground>(0)
3767                                                .capture_all()
3768                                                .on_down(
3769                                                    MouseButton::Left,
3770                                                    |_, this: &mut Self, cx| {
3771                                                        this.zoom_out(cx);
3772                                                    },
3773                                                ),
3774                                        )
3775                                    }))
3776                                    .with_children(self.modal.as_ref().map(|modal| {
3777                                        // Prevent clicks within the modal from falling
3778                                        // through to the rest of the workspace.
3779                                        enum ModalBackground {}
3780                                        MouseEventHandler::new::<ModalBackground, _>(
3781                                            0,
3782                                            cx,
3783                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3784                                        )
3785                                        .on_click(MouseButton::Left, |_, _, _| {})
3786                                        .contained()
3787                                        .with_style(theme.workspace.modal)
3788                                        .aligned()
3789                                        .top()
3790                                    }))
3791                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3792                            ))
3793                            .provide_resize_bounds::<WorkspaceBounds>()
3794                            .flex(1.0, true),
3795                    )
3796                    .with_child(ChildView::new(&self.status_bar, cx))
3797                    .contained()
3798                    .with_background_color(theme.workspace.background),
3799            )
3800            .with_children(DragAndDrop::render(cx))
3801            .with_children(self.render_disconnected_overlay(cx))
3802            .into_any_named("workspace")
3803    }
3804
3805    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3806        if cx.is_self_focused() {
3807            cx.focus(&self.active_pane);
3808        }
3809    }
3810}
3811
3812impl ViewId {
3813    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3814        Ok(Self {
3815            creator: message
3816                .creator
3817                .ok_or_else(|| anyhow!("creator is missing"))?,
3818            id: message.id,
3819        })
3820    }
3821
3822    pub(crate) fn to_proto(&self) -> proto::ViewId {
3823        proto::ViewId {
3824            creator: Some(self.creator),
3825            id: self.id,
3826        }
3827    }
3828}
3829
3830pub trait WorkspaceHandle {
3831    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3832}
3833
3834impl WorkspaceHandle for ViewHandle<Workspace> {
3835    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3836        self.read(cx)
3837            .worktrees(cx)
3838            .flat_map(|worktree| {
3839                let worktree_id = worktree.read(cx).id();
3840                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3841                    worktree_id,
3842                    path: f.path.clone(),
3843                })
3844            })
3845            .collect::<Vec<_>>()
3846    }
3847}
3848
3849impl std::fmt::Debug for OpenPaths {
3850    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3851        f.debug_struct("OpenPaths")
3852            .field("paths", &self.paths)
3853            .finish()
3854    }
3855}
3856
3857pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3858
3859pub fn activate_workspace_for_project(
3860    cx: &mut AsyncAppContext,
3861    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3862) -> Option<WeakViewHandle<Workspace>> {
3863    for window in cx.windows() {
3864        let handle = window
3865            .update(cx, |cx| {
3866                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3867                    let project = workspace_handle.read(cx).project.clone();
3868                    if project.update(cx, &predicate) {
3869                        cx.activate_window();
3870                        return Some(workspace_handle.clone());
3871                    }
3872                }
3873                None
3874            })
3875            .flatten();
3876
3877        if let Some(handle) = handle {
3878            return Some(handle.downgrade());
3879        }
3880    }
3881    None
3882}
3883
3884pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3885    DB.last_workspace().await.log_err().flatten()
3886}
3887
3888#[allow(clippy::type_complexity)]
3889pub fn open_paths(
3890    abs_paths: &[PathBuf],
3891    app_state: &Arc<AppState>,
3892    requesting_window: Option<WindowHandle<Workspace>>,
3893    cx: &mut AppContext,
3894) -> Task<
3895    Result<(
3896        WeakViewHandle<Workspace>,
3897        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3898    )>,
3899> {
3900    let app_state = app_state.clone();
3901    let abs_paths = abs_paths.to_vec();
3902    cx.spawn(|mut cx| async move {
3903        // Open paths in existing workspace if possible
3904        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3905            project.contains_paths(&abs_paths, cx)
3906        });
3907
3908        if let Some(existing) = existing {
3909            Ok((
3910                existing.clone(),
3911                existing
3912                    .update(&mut cx, |workspace, cx| {
3913                        workspace.open_paths(abs_paths, true, cx)
3914                    })?
3915                    .await,
3916            ))
3917        } else {
3918            Ok(cx
3919                .update(|cx| {
3920                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
3921                })
3922                .await)
3923        }
3924    })
3925}
3926
3927pub fn open_new(
3928    app_state: &Arc<AppState>,
3929    cx: &mut AppContext,
3930    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3931) -> Task<()> {
3932    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3933    cx.spawn(|mut cx| async move {
3934        let (workspace, opened_paths) = task.await;
3935
3936        workspace
3937            .update(&mut cx, |workspace, cx| {
3938                if opened_paths.is_empty() {
3939                    init(workspace, cx)
3940                }
3941            })
3942            .log_err();
3943    })
3944}
3945
3946pub fn create_and_open_local_file(
3947    path: &'static Path,
3948    cx: &mut ViewContext<Workspace>,
3949    default_content: impl 'static + Send + FnOnce() -> Rope,
3950) -> Task<Result<Box<dyn ItemHandle>>> {
3951    cx.spawn(|workspace, mut cx| async move {
3952        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3953        if !fs.is_file(path).await {
3954            fs.create_file(path, Default::default()).await?;
3955            fs.save(path, &default_content(), Default::default())
3956                .await?;
3957        }
3958
3959        let mut items = workspace
3960            .update(&mut cx, |workspace, cx| {
3961                workspace.with_local_workspace(cx, |workspace, cx| {
3962                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3963                })
3964            })?
3965            .await?
3966            .await;
3967
3968        let item = items.pop().flatten();
3969        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3970    })
3971}
3972
3973pub fn join_remote_project(
3974    project_id: u64,
3975    follow_user_id: u64,
3976    app_state: Arc<AppState>,
3977    cx: &mut AppContext,
3978) -> Task<Result<()>> {
3979    cx.spawn(|mut cx| async move {
3980        let existing_workspace = cx
3981            .windows()
3982            .into_iter()
3983            .find_map(|window| {
3984                window.downcast::<Workspace>().and_then(|window| {
3985                    window.read_root_with(&cx, |workspace, cx| {
3986                        if workspace.project().read(cx).remote_id() == Some(project_id) {
3987                            Some(cx.handle().downgrade())
3988                        } else {
3989                            None
3990                        }
3991                    })
3992                })
3993            })
3994            .flatten();
3995
3996        let workspace = if let Some(existing_workspace) = existing_workspace {
3997            existing_workspace
3998        } else {
3999            let active_call = cx.read(ActiveCall::global);
4000            let room = active_call
4001                .read_with(&cx, |call, _| call.room().cloned())
4002                .ok_or_else(|| anyhow!("not in a call"))?;
4003            let project = room
4004                .update(&mut cx, |room, cx| {
4005                    room.join_project(
4006                        project_id,
4007                        app_state.languages.clone(),
4008                        app_state.fs.clone(),
4009                        cx,
4010                    )
4011                })
4012                .await?;
4013
4014            let window_bounds_override = window_bounds_env_override(&cx);
4015            let window = cx.add_window(
4016                (app_state.build_window_options)(
4017                    window_bounds_override,
4018                    None,
4019                    cx.platform().as_ref(),
4020                ),
4021                |cx| Workspace::new(0, project, app_state.clone(), cx),
4022            );
4023            let workspace = window.root(&cx).unwrap();
4024            (app_state.initialize_workspace)(
4025                workspace.downgrade(),
4026                false,
4027                app_state.clone(),
4028                cx.clone(),
4029            )
4030            .await
4031            .log_err();
4032
4033            workspace.downgrade()
4034        };
4035
4036        workspace.window().activate(&mut cx);
4037        cx.platform().activate(true);
4038
4039        workspace.update(&mut cx, |workspace, cx| {
4040            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4041                let follow_peer_id = room
4042                    .read(cx)
4043                    .remote_participants()
4044                    .iter()
4045                    .find(|(_, participant)| participant.user.id == follow_user_id)
4046                    .map(|(_, p)| p.peer_id)
4047                    .or_else(|| {
4048                        // If we couldn't follow the given user, follow the host instead.
4049                        let collaborator = workspace
4050                            .project()
4051                            .read(cx)
4052                            .collaborators()
4053                            .values()
4054                            .find(|collaborator| collaborator.replica_id == 0)?;
4055                        Some(collaborator.peer_id)
4056                    });
4057
4058                if let Some(follow_peer_id) = follow_peer_id {
4059                    if !workspace.is_being_followed(follow_peer_id) {
4060                        workspace
4061                            .toggle_follow(follow_peer_id, cx)
4062                            .map(|follow| follow.detach_and_log_err(cx));
4063                    }
4064                }
4065            }
4066        })?;
4067
4068        anyhow::Ok(())
4069    })
4070}
4071
4072pub fn restart(_: &Restart, cx: &mut AppContext) {
4073    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4074    cx.spawn(|mut cx| async move {
4075        let mut workspace_windows = cx
4076            .windows()
4077            .into_iter()
4078            .filter_map(|window| window.downcast::<Workspace>())
4079            .collect::<Vec<_>>();
4080
4081        // If multiple windows have unsaved changes, and need a save prompt,
4082        // prompt in the active window before switching to a different window.
4083        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4084
4085        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4086            let answer = window.prompt(
4087                PromptLevel::Info,
4088                "Are you sure you want to restart?",
4089                &["Restart", "Cancel"],
4090                &mut cx,
4091            );
4092
4093            if let Some(mut answer) = answer {
4094                let answer = answer.next().await;
4095                if answer != Some(0) {
4096                    return Ok(());
4097                }
4098            }
4099        }
4100
4101        // If the user cancels any save prompt, then keep the app open.
4102        for window in workspace_windows {
4103            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4104                workspace.prepare_to_close(true, cx)
4105            }) {
4106                if !should_close.await? {
4107                    return Ok(());
4108                }
4109            }
4110        }
4111        cx.platform().restart();
4112        anyhow::Ok(())
4113    })
4114    .detach_and_log_err(cx);
4115}
4116
4117fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4118    let mut parts = value.split(',');
4119    let width: usize = parts.next()?.parse().ok()?;
4120    let height: usize = parts.next()?.parse().ok()?;
4121    Some(vec2f(width as f32, height as f32))
4122}
4123
4124#[cfg(test)]
4125mod tests {
4126    use super::*;
4127    use crate::{
4128        dock::test::{TestPanel, TestPanelEvent},
4129        item::test::{TestItem, TestItemEvent, TestProjectItem},
4130    };
4131    use fs::FakeFs;
4132    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4133    use project::{Project, ProjectEntryId};
4134    use serde_json::json;
4135    use settings::SettingsStore;
4136    use std::{cell::RefCell, rc::Rc};
4137
4138    #[gpui::test]
4139    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4140        init_test(cx);
4141
4142        let fs = FakeFs::new(cx.background());
4143        let project = Project::test(fs, [], cx).await;
4144        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4145        let workspace = window.root(cx);
4146
4147        // Adding an item with no ambiguity renders the tab without detail.
4148        let item1 = window.add_view(cx, |_| {
4149            let mut item = TestItem::new();
4150            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4151            item
4152        });
4153        workspace.update(cx, |workspace, cx| {
4154            workspace.add_item(Box::new(item1.clone()), cx);
4155        });
4156        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4157
4158        // Adding an item that creates ambiguity increases the level of detail on
4159        // both tabs.
4160        let item2 = window.add_view(cx, |_| {
4161            let mut item = TestItem::new();
4162            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4163            item
4164        });
4165        workspace.update(cx, |workspace, cx| {
4166            workspace.add_item(Box::new(item2.clone()), cx);
4167        });
4168        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4169        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4170
4171        // Adding an item that creates ambiguity increases the level of detail only
4172        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4173        // we stop at the highest detail available.
4174        let item3 = window.add_view(cx, |_| {
4175            let mut item = TestItem::new();
4176            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4177            item
4178        });
4179        workspace.update(cx, |workspace, cx| {
4180            workspace.add_item(Box::new(item3.clone()), cx);
4181        });
4182        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4183        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4184        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4185    }
4186
4187    #[gpui::test]
4188    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4189        init_test(cx);
4190
4191        let fs = FakeFs::new(cx.background());
4192        fs.insert_tree(
4193            "/root1",
4194            json!({
4195                "one.txt": "",
4196                "two.txt": "",
4197            }),
4198        )
4199        .await;
4200        fs.insert_tree(
4201            "/root2",
4202            json!({
4203                "three.txt": "",
4204            }),
4205        )
4206        .await;
4207
4208        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4209        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4210        let workspace = window.root(cx);
4211        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4212        let worktree_id = project.read_with(cx, |project, cx| {
4213            project.worktrees(cx).next().unwrap().read(cx).id()
4214        });
4215
4216        let item1 = window.add_view(cx, |cx| {
4217            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4218        });
4219        let item2 = window.add_view(cx, |cx| {
4220            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4221        });
4222
4223        // Add an item to an empty pane
4224        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4225        project.read_with(cx, |project, cx| {
4226            assert_eq!(
4227                project.active_entry(),
4228                project
4229                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4230                    .map(|e| e.id)
4231            );
4232        });
4233        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4234
4235        // Add a second item to a non-empty pane
4236        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4237        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4238        project.read_with(cx, |project, cx| {
4239            assert_eq!(
4240                project.active_entry(),
4241                project
4242                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4243                    .map(|e| e.id)
4244            );
4245        });
4246
4247        // Close the active item
4248        pane.update(cx, |pane, cx| {
4249            pane.close_active_item(&Default::default(), cx).unwrap()
4250        })
4251        .await
4252        .unwrap();
4253        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4254        project.read_with(cx, |project, cx| {
4255            assert_eq!(
4256                project.active_entry(),
4257                project
4258                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4259                    .map(|e| e.id)
4260            );
4261        });
4262
4263        // Add a project folder
4264        project
4265            .update(cx, |project, cx| {
4266                project.find_or_create_local_worktree("/root2", true, cx)
4267            })
4268            .await
4269            .unwrap();
4270        assert_eq!(
4271            window.current_title(cx).as_deref(),
4272            Some("one.txt β€” root1, root2")
4273        );
4274
4275        // Remove a project folder
4276        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4277        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4278    }
4279
4280    #[gpui::test]
4281    async fn test_close_window(cx: &mut TestAppContext) {
4282        init_test(cx);
4283
4284        let fs = FakeFs::new(cx.background());
4285        fs.insert_tree("/root", json!({ "one": "" })).await;
4286
4287        let project = Project::test(fs, ["root".as_ref()], cx).await;
4288        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4289        let workspace = window.root(cx);
4290
4291        // When there are no dirty items, there's nothing to do.
4292        let item1 = window.add_view(cx, |_| TestItem::new());
4293        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4294        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4295        assert!(task.await.unwrap());
4296
4297        // When there are dirty untitled items, prompt to save each one. If the user
4298        // cancels any prompt, then abort.
4299        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4300        let item3 = window.add_view(cx, |cx| {
4301            TestItem::new()
4302                .with_dirty(true)
4303                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4304        });
4305        workspace.update(cx, |w, cx| {
4306            w.add_item(Box::new(item2.clone()), cx);
4307            w.add_item(Box::new(item3.clone()), cx);
4308        });
4309        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4310        cx.foreground().run_until_parked();
4311        window.simulate_prompt_answer(2, cx); // cancel
4312        cx.foreground().run_until_parked();
4313        assert!(!window.has_pending_prompt(cx));
4314        assert!(!task.await.unwrap());
4315    }
4316
4317    #[gpui::test]
4318    async fn test_close_pane_items(cx: &mut TestAppContext) {
4319        init_test(cx);
4320
4321        let fs = FakeFs::new(cx.background());
4322
4323        let project = Project::test(fs, None, cx).await;
4324        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4325        let workspace = window.root(cx);
4326
4327        let item1 = window.add_view(cx, |cx| {
4328            TestItem::new()
4329                .with_dirty(true)
4330                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4331        });
4332        let item2 = window.add_view(cx, |cx| {
4333            TestItem::new()
4334                .with_dirty(true)
4335                .with_conflict(true)
4336                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4337        });
4338        let item3 = window.add_view(cx, |cx| {
4339            TestItem::new()
4340                .with_dirty(true)
4341                .with_conflict(true)
4342                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4343        });
4344        let item4 = window.add_view(cx, |cx| {
4345            TestItem::new()
4346                .with_dirty(true)
4347                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4348        });
4349        let pane = workspace.update(cx, |workspace, cx| {
4350            workspace.add_item(Box::new(item1.clone()), cx);
4351            workspace.add_item(Box::new(item2.clone()), cx);
4352            workspace.add_item(Box::new(item3.clone()), cx);
4353            workspace.add_item(Box::new(item4.clone()), cx);
4354            workspace.active_pane().clone()
4355        });
4356
4357        let close_items = pane.update(cx, |pane, cx| {
4358            pane.activate_item(1, true, true, cx);
4359            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4360            let item1_id = item1.id();
4361            let item3_id = item3.id();
4362            let item4_id = item4.id();
4363            pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| {
4364                [item1_id, item3_id, item4_id].contains(&id)
4365            })
4366        });
4367        cx.foreground().run_until_parked();
4368
4369        // There's a prompt to save item 1.
4370        pane.read_with(cx, |pane, _| {
4371            assert_eq!(pane.items_len(), 4);
4372            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4373        });
4374        assert!(window.has_pending_prompt(cx));
4375
4376        // Confirm saving item 1.
4377        window.simulate_prompt_answer(0, cx);
4378        cx.foreground().run_until_parked();
4379
4380        // Item 1 is saved. There's a prompt to save item 3.
4381        pane.read_with(cx, |pane, cx| {
4382            assert_eq!(item1.read(cx).save_count, 1);
4383            assert_eq!(item1.read(cx).save_as_count, 0);
4384            assert_eq!(item1.read(cx).reload_count, 0);
4385            assert_eq!(pane.items_len(), 3);
4386            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4387        });
4388        assert!(window.has_pending_prompt(cx));
4389
4390        // Cancel saving item 3.
4391        window.simulate_prompt_answer(1, cx);
4392        cx.foreground().run_until_parked();
4393
4394        // Item 3 is reloaded. There's a prompt to save item 4.
4395        pane.read_with(cx, |pane, cx| {
4396            assert_eq!(item3.read(cx).save_count, 0);
4397            assert_eq!(item3.read(cx).save_as_count, 0);
4398            assert_eq!(item3.read(cx).reload_count, 1);
4399            assert_eq!(pane.items_len(), 2);
4400            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4401        });
4402        assert!(window.has_pending_prompt(cx));
4403
4404        // Confirm saving item 4.
4405        window.simulate_prompt_answer(0, cx);
4406        cx.foreground().run_until_parked();
4407
4408        // There's a prompt for a path for item 4.
4409        cx.simulate_new_path_selection(|_| Some(Default::default()));
4410        close_items.await.unwrap();
4411
4412        // The requested items are closed.
4413        pane.read_with(cx, |pane, cx| {
4414            assert_eq!(item4.read(cx).save_count, 0);
4415            assert_eq!(item4.read(cx).save_as_count, 1);
4416            assert_eq!(item4.read(cx).reload_count, 0);
4417            assert_eq!(pane.items_len(), 1);
4418            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4419        });
4420    }
4421
4422    #[gpui::test]
4423    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4424        init_test(cx);
4425
4426        let fs = FakeFs::new(cx.background());
4427
4428        let project = Project::test(fs, [], cx).await;
4429        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4430        let workspace = window.root(cx);
4431
4432        // Create several workspace items with single project entries, and two
4433        // workspace items with multiple project entries.
4434        let single_entry_items = (0..=4)
4435            .map(|project_entry_id| {
4436                window.add_view(cx, |cx| {
4437                    TestItem::new()
4438                        .with_dirty(true)
4439                        .with_project_items(&[TestProjectItem::new(
4440                            project_entry_id,
4441                            &format!("{project_entry_id}.txt"),
4442                            cx,
4443                        )])
4444                })
4445            })
4446            .collect::<Vec<_>>();
4447        let item_2_3 = window.add_view(cx, |cx| {
4448            TestItem::new()
4449                .with_dirty(true)
4450                .with_singleton(false)
4451                .with_project_items(&[
4452                    single_entry_items[2].read(cx).project_items[0].clone(),
4453                    single_entry_items[3].read(cx).project_items[0].clone(),
4454                ])
4455        });
4456        let item_3_4 = window.add_view(cx, |cx| {
4457            TestItem::new()
4458                .with_dirty(true)
4459                .with_singleton(false)
4460                .with_project_items(&[
4461                    single_entry_items[3].read(cx).project_items[0].clone(),
4462                    single_entry_items[4].read(cx).project_items[0].clone(),
4463                ])
4464        });
4465
4466        // Create two panes that contain the following project entries:
4467        //   left pane:
4468        //     multi-entry items:   (2, 3)
4469        //     single-entry items:  0, 1, 2, 3, 4
4470        //   right pane:
4471        //     single-entry items:  1
4472        //     multi-entry items:   (3, 4)
4473        let left_pane = workspace.update(cx, |workspace, cx| {
4474            let left_pane = workspace.active_pane().clone();
4475            workspace.add_item(Box::new(item_2_3.clone()), cx);
4476            for item in single_entry_items {
4477                workspace.add_item(Box::new(item), cx);
4478            }
4479            left_pane.update(cx, |pane, cx| {
4480                pane.activate_item(2, true, true, cx);
4481            });
4482
4483            workspace
4484                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4485                .unwrap();
4486
4487            left_pane
4488        });
4489
4490        //Need to cause an effect flush in order to respect new focus
4491        workspace.update(cx, |workspace, cx| {
4492            workspace.add_item(Box::new(item_3_4.clone()), cx);
4493            cx.focus(&left_pane);
4494        });
4495
4496        // When closing all of the items in the left pane, we should be prompted twice:
4497        // once for project entry 0, and once for project entry 2. After those two
4498        // prompts, the task should complete.
4499
4500        let close = left_pane.update(cx, |pane, cx| {
4501            pane.close_items(cx, SaveBehavior::PromptOnWrite, move |_| true)
4502        });
4503        cx.foreground().run_until_parked();
4504        left_pane.read_with(cx, |pane, cx| {
4505            assert_eq!(
4506                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4507                &[ProjectEntryId::from_proto(0)]
4508            );
4509        });
4510        window.simulate_prompt_answer(0, cx);
4511
4512        cx.foreground().run_until_parked();
4513        left_pane.read_with(cx, |pane, cx| {
4514            assert_eq!(
4515                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4516                &[ProjectEntryId::from_proto(2)]
4517            );
4518        });
4519        window.simulate_prompt_answer(0, cx);
4520
4521        cx.foreground().run_until_parked();
4522        close.await.unwrap();
4523        left_pane.read_with(cx, |pane, _| {
4524            assert_eq!(pane.items_len(), 0);
4525        });
4526    }
4527
4528    #[gpui::test]
4529    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4530        init_test(cx);
4531
4532        let fs = FakeFs::new(cx.background());
4533
4534        let project = Project::test(fs, [], cx).await;
4535        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4536        let workspace = window.root(cx);
4537        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4538
4539        let item = window.add_view(cx, |cx| {
4540            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4541        });
4542        let item_id = item.id();
4543        workspace.update(cx, |workspace, cx| {
4544            workspace.add_item(Box::new(item.clone()), cx);
4545        });
4546
4547        // Autosave on window change.
4548        item.update(cx, |item, cx| {
4549            cx.update_global(|settings: &mut SettingsStore, cx| {
4550                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4551                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4552                })
4553            });
4554            item.is_dirty = true;
4555        });
4556
4557        // Deactivating the window saves the file.
4558        window.simulate_deactivation(cx);
4559        deterministic.run_until_parked();
4560        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4561
4562        // Autosave on focus change.
4563        item.update(cx, |item, cx| {
4564            cx.focus_self();
4565            cx.update_global(|settings: &mut SettingsStore, cx| {
4566                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4567                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4568                })
4569            });
4570            item.is_dirty = true;
4571        });
4572
4573        // Blurring the item saves the file.
4574        item.update(cx, |_, cx| cx.blur());
4575        deterministic.run_until_parked();
4576        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4577
4578        // Deactivating the window still saves the file.
4579        window.simulate_activation(cx);
4580        item.update(cx, |item, cx| {
4581            cx.focus_self();
4582            item.is_dirty = true;
4583        });
4584        window.simulate_deactivation(cx);
4585
4586        deterministic.run_until_parked();
4587        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4588
4589        // Autosave after delay.
4590        item.update(cx, |item, cx| {
4591            cx.update_global(|settings: &mut SettingsStore, cx| {
4592                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4593                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4594                })
4595            });
4596            item.is_dirty = true;
4597            cx.emit(TestItemEvent::Edit);
4598        });
4599
4600        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4601        deterministic.advance_clock(Duration::from_millis(250));
4602        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4603
4604        // After delay expires, the file is saved.
4605        deterministic.advance_clock(Duration::from_millis(250));
4606        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4607
4608        // Autosave on focus change, ensuring closing the tab counts as such.
4609        item.update(cx, |item, cx| {
4610            cx.update_global(|settings: &mut SettingsStore, cx| {
4611                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4612                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4613                })
4614            });
4615            item.is_dirty = true;
4616        });
4617
4618        pane.update(cx, |pane, cx| {
4619            pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id)
4620        })
4621        .await
4622        .unwrap();
4623        assert!(!window.has_pending_prompt(cx));
4624        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4625
4626        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4627        workspace.update(cx, |workspace, cx| {
4628            workspace.add_item(Box::new(item.clone()), cx);
4629        });
4630        item.update(cx, |item, cx| {
4631            item.project_items[0].update(cx, |item, _| {
4632                item.entry_id = None;
4633            });
4634            item.is_dirty = true;
4635            cx.blur();
4636        });
4637        deterministic.run_until_parked();
4638        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4639
4640        // Ensure autosave is prevented for deleted files also when closing the buffer.
4641        let _close_items = pane.update(cx, |pane, cx| {
4642            pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id)
4643        });
4644        deterministic.run_until_parked();
4645        assert!(window.has_pending_prompt(cx));
4646        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4647    }
4648
4649    #[gpui::test]
4650    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4651        init_test(cx);
4652
4653        let fs = FakeFs::new(cx.background());
4654
4655        let project = Project::test(fs, [], cx).await;
4656        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4657        let workspace = window.root(cx);
4658
4659        let item = window.add_view(cx, |cx| {
4660            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4661        });
4662        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4663        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4664        let toolbar_notify_count = Rc::new(RefCell::new(0));
4665
4666        workspace.update(cx, |workspace, cx| {
4667            workspace.add_item(Box::new(item.clone()), cx);
4668            let toolbar_notification_count = toolbar_notify_count.clone();
4669            cx.observe(&toolbar, move |_, _, _| {
4670                *toolbar_notification_count.borrow_mut() += 1
4671            })
4672            .detach();
4673        });
4674
4675        pane.read_with(cx, |pane, _| {
4676            assert!(!pane.can_navigate_backward());
4677            assert!(!pane.can_navigate_forward());
4678        });
4679
4680        item.update(cx, |item, cx| {
4681            item.set_state("one".to_string(), cx);
4682        });
4683
4684        // Toolbar must be notified to re-render the navigation buttons
4685        assert_eq!(*toolbar_notify_count.borrow(), 1);
4686
4687        pane.read_with(cx, |pane, _| {
4688            assert!(pane.can_navigate_backward());
4689            assert!(!pane.can_navigate_forward());
4690        });
4691
4692        workspace
4693            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4694            .await
4695            .unwrap();
4696
4697        assert_eq!(*toolbar_notify_count.borrow(), 3);
4698        pane.read_with(cx, |pane, _| {
4699            assert!(!pane.can_navigate_backward());
4700            assert!(pane.can_navigate_forward());
4701        });
4702    }
4703
4704    #[gpui::test]
4705    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4706        init_test(cx);
4707        let fs = FakeFs::new(cx.background());
4708
4709        let project = Project::test(fs, [], cx).await;
4710        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4711        let workspace = window.root(cx);
4712
4713        let panel = workspace.update(cx, |workspace, cx| {
4714            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4715            workspace.add_panel(panel.clone(), cx);
4716
4717            workspace
4718                .right_dock()
4719                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4720
4721            panel
4722        });
4723
4724        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4725        pane.update(cx, |pane, cx| {
4726            let item = cx.add_view(|_| TestItem::new());
4727            pane.add_item(Box::new(item), true, true, None, cx);
4728        });
4729
4730        // Transfer focus from center to panel
4731        workspace.update(cx, |workspace, cx| {
4732            workspace.toggle_panel_focus::<TestPanel>(cx);
4733        });
4734
4735        workspace.read_with(cx, |workspace, cx| {
4736            assert!(workspace.right_dock().read(cx).is_open());
4737            assert!(!panel.is_zoomed(cx));
4738            assert!(panel.has_focus(cx));
4739        });
4740
4741        // Transfer focus from panel to center
4742        workspace.update(cx, |workspace, cx| {
4743            workspace.toggle_panel_focus::<TestPanel>(cx);
4744        });
4745
4746        workspace.read_with(cx, |workspace, cx| {
4747            assert!(workspace.right_dock().read(cx).is_open());
4748            assert!(!panel.is_zoomed(cx));
4749            assert!(!panel.has_focus(cx));
4750        });
4751
4752        // Close the dock
4753        workspace.update(cx, |workspace, cx| {
4754            workspace.toggle_dock(DockPosition::Right, cx);
4755        });
4756
4757        workspace.read_with(cx, |workspace, cx| {
4758            assert!(!workspace.right_dock().read(cx).is_open());
4759            assert!(!panel.is_zoomed(cx));
4760            assert!(!panel.has_focus(cx));
4761        });
4762
4763        // Open the dock
4764        workspace.update(cx, |workspace, cx| {
4765            workspace.toggle_dock(DockPosition::Right, cx);
4766        });
4767
4768        workspace.read_with(cx, |workspace, cx| {
4769            assert!(workspace.right_dock().read(cx).is_open());
4770            assert!(!panel.is_zoomed(cx));
4771            assert!(panel.has_focus(cx));
4772        });
4773
4774        // Focus and zoom panel
4775        panel.update(cx, |panel, cx| {
4776            cx.focus_self();
4777            panel.set_zoomed(true, cx)
4778        });
4779
4780        workspace.read_with(cx, |workspace, cx| {
4781            assert!(workspace.right_dock().read(cx).is_open());
4782            assert!(panel.is_zoomed(cx));
4783            assert!(panel.has_focus(cx));
4784        });
4785
4786        // Transfer focus to the center closes the dock
4787        workspace.update(cx, |workspace, cx| {
4788            workspace.toggle_panel_focus::<TestPanel>(cx);
4789        });
4790
4791        workspace.read_with(cx, |workspace, cx| {
4792            assert!(!workspace.right_dock().read(cx).is_open());
4793            assert!(panel.is_zoomed(cx));
4794            assert!(!panel.has_focus(cx));
4795        });
4796
4797        // Transferring focus back to the panel keeps it zoomed
4798        workspace.update(cx, |workspace, cx| {
4799            workspace.toggle_panel_focus::<TestPanel>(cx);
4800        });
4801
4802        workspace.read_with(cx, |workspace, cx| {
4803            assert!(workspace.right_dock().read(cx).is_open());
4804            assert!(panel.is_zoomed(cx));
4805            assert!(panel.has_focus(cx));
4806        });
4807
4808        // Close the dock while it is zoomed
4809        workspace.update(cx, |workspace, cx| {
4810            workspace.toggle_dock(DockPosition::Right, cx)
4811        });
4812
4813        workspace.read_with(cx, |workspace, cx| {
4814            assert!(!workspace.right_dock().read(cx).is_open());
4815            assert!(panel.is_zoomed(cx));
4816            assert!(workspace.zoomed.is_none());
4817            assert!(!panel.has_focus(cx));
4818        });
4819
4820        // Opening the dock, when it's zoomed, retains focus
4821        workspace.update(cx, |workspace, cx| {
4822            workspace.toggle_dock(DockPosition::Right, cx)
4823        });
4824
4825        workspace.read_with(cx, |workspace, cx| {
4826            assert!(workspace.right_dock().read(cx).is_open());
4827            assert!(panel.is_zoomed(cx));
4828            assert!(workspace.zoomed.is_some());
4829            assert!(panel.has_focus(cx));
4830        });
4831
4832        // Unzoom and close the panel, zoom the active pane.
4833        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4834        workspace.update(cx, |workspace, cx| {
4835            workspace.toggle_dock(DockPosition::Right, cx)
4836        });
4837        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4838
4839        // Opening a dock unzooms the pane.
4840        workspace.update(cx, |workspace, cx| {
4841            workspace.toggle_dock(DockPosition::Right, cx)
4842        });
4843        workspace.read_with(cx, |workspace, cx| {
4844            let pane = pane.read(cx);
4845            assert!(!pane.is_zoomed());
4846            assert!(!pane.has_focus());
4847            assert!(workspace.right_dock().read(cx).is_open());
4848            assert!(workspace.zoomed.is_none());
4849        });
4850    }
4851
4852    #[gpui::test]
4853    async fn test_panels(cx: &mut gpui::TestAppContext) {
4854        init_test(cx);
4855        let fs = FakeFs::new(cx.background());
4856
4857        let project = Project::test(fs, [], cx).await;
4858        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4859        let workspace = window.root(cx);
4860
4861        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4862            // Add panel_1 on the left, panel_2 on the right.
4863            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4864            workspace.add_panel(panel_1.clone(), cx);
4865            workspace
4866                .left_dock()
4867                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4868            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4869            workspace.add_panel(panel_2.clone(), cx);
4870            workspace
4871                .right_dock()
4872                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4873
4874            let left_dock = workspace.left_dock();
4875            assert_eq!(
4876                left_dock.read(cx).visible_panel().unwrap().id(),
4877                panel_1.id()
4878            );
4879            assert_eq!(
4880                left_dock.read(cx).active_panel_size(cx).unwrap(),
4881                panel_1.size(cx)
4882            );
4883
4884            left_dock.update(cx, |left_dock, cx| {
4885                left_dock.resize_active_panel(Some(1337.), cx)
4886            });
4887            assert_eq!(
4888                workspace
4889                    .right_dock()
4890                    .read(cx)
4891                    .visible_panel()
4892                    .unwrap()
4893                    .id(),
4894                panel_2.id()
4895            );
4896
4897            (panel_1, panel_2)
4898        });
4899
4900        // Move panel_1 to the right
4901        panel_1.update(cx, |panel_1, cx| {
4902            panel_1.set_position(DockPosition::Right, cx)
4903        });
4904
4905        workspace.update(cx, |workspace, cx| {
4906            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4907            // Since it was the only panel on the left, the left dock should now be closed.
4908            assert!(!workspace.left_dock().read(cx).is_open());
4909            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4910            let right_dock = workspace.right_dock();
4911            assert_eq!(
4912                right_dock.read(cx).visible_panel().unwrap().id(),
4913                panel_1.id()
4914            );
4915            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4916
4917            // Now we move panel_2Β to the left
4918            panel_2.set_position(DockPosition::Left, cx);
4919        });
4920
4921        workspace.update(cx, |workspace, cx| {
4922            // Since panel_2 was not visible on the right, we don't open the left dock.
4923            assert!(!workspace.left_dock().read(cx).is_open());
4924            // And the right dock is unaffected in it's displaying of panel_1
4925            assert!(workspace.right_dock().read(cx).is_open());
4926            assert_eq!(
4927                workspace
4928                    .right_dock()
4929                    .read(cx)
4930                    .visible_panel()
4931                    .unwrap()
4932                    .id(),
4933                panel_1.id()
4934            );
4935        });
4936
4937        // Move panel_1 back to the left
4938        panel_1.update(cx, |panel_1, cx| {
4939            panel_1.set_position(DockPosition::Left, cx)
4940        });
4941
4942        workspace.update(cx, |workspace, cx| {
4943            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4944            let left_dock = workspace.left_dock();
4945            assert!(left_dock.read(cx).is_open());
4946            assert_eq!(
4947                left_dock.read(cx).visible_panel().unwrap().id(),
4948                panel_1.id()
4949            );
4950            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4951            // And right the dock should be closed as it no longer has any panels.
4952            assert!(!workspace.right_dock().read(cx).is_open());
4953
4954            // Now we move panel_1 to the bottom
4955            panel_1.set_position(DockPosition::Bottom, cx);
4956        });
4957
4958        workspace.update(cx, |workspace, cx| {
4959            // Since panel_1 was visible on the left, we close the left dock.
4960            assert!(!workspace.left_dock().read(cx).is_open());
4961            // The bottom dock is sized based on the panel's default size,
4962            // since the panel orientation changed from vertical to horizontal.
4963            let bottom_dock = workspace.bottom_dock();
4964            assert_eq!(
4965                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4966                panel_1.size(cx),
4967            );
4968            // Close bottom dock and move panel_1 back to the left.
4969            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4970            panel_1.set_position(DockPosition::Left, cx);
4971        });
4972
4973        // Emit activated event on panel 1
4974        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4975
4976        // Now the left dock is open and panel_1 is active and focused.
4977        workspace.read_with(cx, |workspace, cx| {
4978            let left_dock = workspace.left_dock();
4979            assert!(left_dock.read(cx).is_open());
4980            assert_eq!(
4981                left_dock.read(cx).visible_panel().unwrap().id(),
4982                panel_1.id()
4983            );
4984            assert!(panel_1.is_focused(cx));
4985        });
4986
4987        // Emit closed event on panel 2, which is not active
4988        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4989
4990        // Wo don't close the left dock, because panel_2 wasn't the active panel
4991        workspace.read_with(cx, |workspace, cx| {
4992            let left_dock = workspace.left_dock();
4993            assert!(left_dock.read(cx).is_open());
4994            assert_eq!(
4995                left_dock.read(cx).visible_panel().unwrap().id(),
4996                panel_1.id()
4997            );
4998        });
4999
5000        // Emitting a ZoomIn event shows the panel as zoomed.
5001        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5002        workspace.read_with(cx, |workspace, _| {
5003            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5004            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5005        });
5006
5007        // Move panel to another dock while it is zoomed
5008        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5009        workspace.read_with(cx, |workspace, _| {
5010            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5011            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5012        });
5013
5014        // If focus is transferred to another view that's not a panel or another pane, we still show
5015        // the panel as zoomed.
5016        let focus_receiver = window.add_view(cx, |_| EmptyView);
5017        focus_receiver.update(cx, |_, cx| cx.focus_self());
5018        workspace.read_with(cx, |workspace, _| {
5019            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5020            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5021        });
5022
5023        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5024        workspace.update(cx, |_, cx| cx.focus_self());
5025        workspace.read_with(cx, |workspace, _| {
5026            assert_eq!(workspace.zoomed, None);
5027            assert_eq!(workspace.zoomed_position, None);
5028        });
5029
5030        // If focus is transferred again to another view that's not a panel or a pane, we won't
5031        // show the panel as zoomed because it wasn't zoomed before.
5032        focus_receiver.update(cx, |_, cx| cx.focus_self());
5033        workspace.read_with(cx, |workspace, _| {
5034            assert_eq!(workspace.zoomed, None);
5035            assert_eq!(workspace.zoomed_position, None);
5036        });
5037
5038        // When focus is transferred back to the panel, it is zoomed again.
5039        panel_1.update(cx, |_, cx| cx.focus_self());
5040        workspace.read_with(cx, |workspace, _| {
5041            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5042            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5043        });
5044
5045        // Emitting a ZoomOut event unzooms the panel.
5046        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5047        workspace.read_with(cx, |workspace, _| {
5048            assert_eq!(workspace.zoomed, None);
5049            assert_eq!(workspace.zoomed_position, None);
5050        });
5051
5052        // Emit closed event on panel 1, which is active
5053        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5054
5055        // Now the left dock is closed, because panel_1 was the active panel
5056        workspace.read_with(cx, |workspace, cx| {
5057            let right_dock = workspace.right_dock();
5058            assert!(!right_dock.read(cx).is_open());
5059        });
5060    }
5061
5062    pub fn init_test(cx: &mut TestAppContext) {
5063        cx.foreground().forbid_parking();
5064        cx.update(|cx| {
5065            cx.set_global(SettingsStore::test(cx));
5066            theme::init((), cx);
5067            language::init(cx);
5068            crate::init_settings(cx);
5069            Project::init_settings(cx);
5070        });
5071    }
5072}