workspace.rs

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