workspace.rs

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