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