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                                        enum ModalBackground {}
3759                                        MouseEventHandler::<ModalBackground, _>::new(
3760                                            0,
3761                                            cx,
3762                                            |_, cx| {
3763                                                ChildView::new(modal.view.as_any(), cx)
3764                                                    .contained()
3765                                                    .with_style(theme.workspace.modal)
3766                                                    .aligned()
3767                                                    .top()
3768                                            },
3769                                        )
3770                                        .on_click(MouseButton::Left, |_, _, _| {})
3771                                        // Consume click events to stop focus dropping through
3772                                    }))
3773                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3774                            ))
3775                            .flex(1.0, true),
3776                    )
3777                    .with_child(ChildView::new(&self.status_bar, cx))
3778                    .contained()
3779                    .with_background_color(theme.workspace.background),
3780            )
3781            .with_children(DragAndDrop::render(cx))
3782            .with_children(self.render_disconnected_overlay(cx))
3783            .into_any_named("workspace")
3784    }
3785
3786    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3787        if cx.is_self_focused() {
3788            cx.focus(&self.active_pane);
3789        }
3790    }
3791}
3792
3793impl ViewId {
3794    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3795        Ok(Self {
3796            creator: message
3797                .creator
3798                .ok_or_else(|| anyhow!("creator is missing"))?,
3799            id: message.id,
3800        })
3801    }
3802
3803    pub(crate) fn to_proto(&self) -> proto::ViewId {
3804        proto::ViewId {
3805            creator: Some(self.creator),
3806            id: self.id,
3807        }
3808    }
3809}
3810
3811pub trait WorkspaceHandle {
3812    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3813}
3814
3815impl WorkspaceHandle for ViewHandle<Workspace> {
3816    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3817        self.read(cx)
3818            .worktrees(cx)
3819            .flat_map(|worktree| {
3820                let worktree_id = worktree.read(cx).id();
3821                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3822                    worktree_id,
3823                    path: f.path.clone(),
3824                })
3825            })
3826            .collect::<Vec<_>>()
3827    }
3828}
3829
3830impl std::fmt::Debug for OpenPaths {
3831    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3832        f.debug_struct("OpenPaths")
3833            .field("paths", &self.paths)
3834            .finish()
3835    }
3836}
3837
3838pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3839
3840pub fn activate_workspace_for_project(
3841    cx: &mut AsyncAppContext,
3842    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3843) -> Option<WeakViewHandle<Workspace>> {
3844    for window_id in cx.window_ids() {
3845        let handle = cx
3846            .update_window(window_id, |cx| {
3847                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3848                    let project = workspace_handle.read(cx).project.clone();
3849                    if project.update(cx, &predicate) {
3850                        cx.activate_window();
3851                        return Some(workspace_handle.clone());
3852                    }
3853                }
3854                None
3855            })
3856            .flatten();
3857
3858        if let Some(handle) = handle {
3859            return Some(handle.downgrade());
3860        }
3861    }
3862    None
3863}
3864
3865pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3866    DB.last_workspace().await.log_err().flatten()
3867}
3868
3869#[allow(clippy::type_complexity)]
3870pub fn open_paths(
3871    abs_paths: &[PathBuf],
3872    app_state: &Arc<AppState>,
3873    requesting_window_id: Option<usize>,
3874    cx: &mut AppContext,
3875) -> Task<
3876    Result<(
3877        WeakViewHandle<Workspace>,
3878        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3879    )>,
3880> {
3881    let app_state = app_state.clone();
3882    let abs_paths = abs_paths.to_vec();
3883    cx.spawn(|mut cx| async move {
3884        // Open paths in existing workspace if possible
3885        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3886            project.contains_paths(&abs_paths, cx)
3887        });
3888
3889        if let Some(existing) = existing {
3890            Ok((
3891                existing.clone(),
3892                existing
3893                    .update(&mut cx, |workspace, cx| {
3894                        workspace.open_paths(abs_paths, true, cx)
3895                    })?
3896                    .await,
3897            ))
3898        } else {
3899            Ok(cx
3900                .update(|cx| {
3901                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3902                })
3903                .await)
3904        }
3905    })
3906}
3907
3908pub fn open_new(
3909    app_state: &Arc<AppState>,
3910    cx: &mut AppContext,
3911    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3912) -> Task<()> {
3913    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3914    cx.spawn(|mut cx| async move {
3915        let (workspace, opened_paths) = task.await;
3916
3917        workspace
3918            .update(&mut cx, |workspace, cx| {
3919                if opened_paths.is_empty() {
3920                    init(workspace, cx)
3921                }
3922            })
3923            .log_err();
3924    })
3925}
3926
3927pub fn create_and_open_local_file(
3928    path: &'static Path,
3929    cx: &mut ViewContext<Workspace>,
3930    default_content: impl 'static + Send + FnOnce() -> Rope,
3931) -> Task<Result<Box<dyn ItemHandle>>> {
3932    cx.spawn(|workspace, mut cx| async move {
3933        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3934        if !fs.is_file(path).await {
3935            fs.create_file(path, Default::default()).await?;
3936            fs.save(path, &default_content(), Default::default())
3937                .await?;
3938        }
3939
3940        let mut items = workspace
3941            .update(&mut cx, |workspace, cx| {
3942                workspace.with_local_workspace(cx, |workspace, cx| {
3943                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3944                })
3945            })?
3946            .await?
3947            .await;
3948
3949        let item = items.pop().flatten();
3950        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3951    })
3952}
3953
3954pub fn join_remote_project(
3955    project_id: u64,
3956    follow_user_id: u64,
3957    app_state: Arc<AppState>,
3958    cx: &mut AppContext,
3959) -> Task<Result<()>> {
3960    cx.spawn(|mut cx| async move {
3961        let existing_workspace = cx
3962            .window_ids()
3963            .into_iter()
3964            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3965            .find(|workspace| {
3966                cx.read_window(workspace.window_id(), |cx| {
3967                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3968                })
3969                .unwrap_or(false)
3970            });
3971
3972        let workspace = if let Some(existing_workspace) = existing_workspace {
3973            existing_workspace.downgrade()
3974        } else {
3975            let active_call = cx.read(ActiveCall::global);
3976            let room = active_call
3977                .read_with(&cx, |call, _| call.room().cloned())
3978                .ok_or_else(|| anyhow!("not in a call"))?;
3979            let project = room
3980                .update(&mut cx, |room, cx| {
3981                    room.join_project(
3982                        project_id,
3983                        app_state.languages.clone(),
3984                        app_state.fs.clone(),
3985                        cx,
3986                    )
3987                })
3988                .await?;
3989
3990            let window_bounds_override = window_bounds_env_override(&cx);
3991            let window = cx.add_window(
3992                (app_state.build_window_options)(
3993                    window_bounds_override,
3994                    None,
3995                    cx.platform().as_ref(),
3996                ),
3997                |cx| Workspace::new(0, project, app_state.clone(), cx),
3998            );
3999            let workspace = window.root(&cx).unwrap();
4000            (app_state.initialize_workspace)(
4001                workspace.downgrade(),
4002                false,
4003                app_state.clone(),
4004                cx.clone(),
4005            )
4006            .await
4007            .log_err();
4008
4009            workspace.downgrade()
4010        };
4011
4012        cx.activate_window(workspace.window_id());
4013        cx.platform().activate(true);
4014
4015        workspace.update(&mut cx, |workspace, cx| {
4016            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4017                let follow_peer_id = room
4018                    .read(cx)
4019                    .remote_participants()
4020                    .iter()
4021                    .find(|(_, participant)| participant.user.id == follow_user_id)
4022                    .map(|(_, p)| p.peer_id)
4023                    .or_else(|| {
4024                        // If we couldn't follow the given user, follow the host instead.
4025                        let collaborator = workspace
4026                            .project()
4027                            .read(cx)
4028                            .collaborators()
4029                            .values()
4030                            .find(|collaborator| collaborator.replica_id == 0)?;
4031                        Some(collaborator.peer_id)
4032                    });
4033
4034                if let Some(follow_peer_id) = follow_peer_id {
4035                    if !workspace.is_being_followed(follow_peer_id) {
4036                        workspace
4037                            .toggle_follow(follow_peer_id, cx)
4038                            .map(|follow| follow.detach_and_log_err(cx));
4039                    }
4040                }
4041            }
4042        })?;
4043
4044        anyhow::Ok(())
4045    })
4046}
4047
4048pub fn restart(_: &Restart, cx: &mut AppContext) {
4049    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4050    cx.spawn(|mut cx| async move {
4051        let mut workspaces = cx
4052            .window_ids()
4053            .into_iter()
4054            .filter_map(|window_id| {
4055                Some(
4056                    cx.root_view(window_id)?
4057                        .clone()
4058                        .downcast::<Workspace>()?
4059                        .downgrade(),
4060                )
4061            })
4062            .collect::<Vec<_>>();
4063
4064        // If multiple windows have unsaved changes, and need a save prompt,
4065        // prompt in the active window before switching to a different window.
4066        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
4067
4068        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
4069            let answer = cx.prompt(
4070                workspace.window_id(),
4071                PromptLevel::Info,
4072                "Are you sure you want to restart?",
4073                &["Restart", "Cancel"],
4074            );
4075
4076            if let Some(mut answer) = answer {
4077                let answer = answer.next().await;
4078                if answer != Some(0) {
4079                    return Ok(());
4080                }
4081            }
4082        }
4083
4084        // If the user cancels any save prompt, then keep the app open.
4085        for workspace in workspaces {
4086            if !workspace
4087                .update(&mut cx, |workspace, cx| {
4088                    workspace.prepare_to_close(true, cx)
4089                })?
4090                .await?
4091            {
4092                return Ok(());
4093            }
4094        }
4095        cx.platform().restart();
4096        anyhow::Ok(())
4097    })
4098    .detach_and_log_err(cx);
4099}
4100
4101fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4102    let mut parts = value.split(',');
4103    let width: usize = parts.next()?.parse().ok()?;
4104    let height: usize = parts.next()?.parse().ok()?;
4105    Some(vec2f(width as f32, height as f32))
4106}
4107
4108#[cfg(test)]
4109mod tests {
4110    use super::*;
4111    use crate::{
4112        dock::test::{TestPanel, TestPanelEvent},
4113        item::test::{TestItem, TestItemEvent, TestProjectItem},
4114    };
4115    use fs::FakeFs;
4116    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4117    use project::{Project, ProjectEntryId};
4118    use serde_json::json;
4119    use settings::SettingsStore;
4120    use std::{cell::RefCell, rc::Rc};
4121
4122    #[gpui::test]
4123    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4124        init_test(cx);
4125
4126        let fs = FakeFs::new(cx.background());
4127        let project = Project::test(fs, [], cx).await;
4128        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4129        let workspace = window.root(cx);
4130
4131        // Adding an item with no ambiguity renders the tab without detail.
4132        let item1 = window.add_view(cx, |_| {
4133            let mut item = TestItem::new();
4134            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4135            item
4136        });
4137        workspace.update(cx, |workspace, cx| {
4138            workspace.add_item(Box::new(item1.clone()), cx);
4139        });
4140        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4141
4142        // Adding an item that creates ambiguity increases the level of detail on
4143        // both tabs.
4144        let item2 = window.add_view(cx, |_| {
4145            let mut item = TestItem::new();
4146            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4147            item
4148        });
4149        workspace.update(cx, |workspace, cx| {
4150            workspace.add_item(Box::new(item2.clone()), cx);
4151        });
4152        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4153        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4154
4155        // Adding an item that creates ambiguity increases the level of detail only
4156        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4157        // we stop at the highest detail available.
4158        let item3 = window.add_view(cx, |_| {
4159            let mut item = TestItem::new();
4160            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4161            item
4162        });
4163        workspace.update(cx, |workspace, cx| {
4164            workspace.add_item(Box::new(item3.clone()), cx);
4165        });
4166        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4167        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4168        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4169    }
4170
4171    #[gpui::test]
4172    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4173        init_test(cx);
4174
4175        let fs = FakeFs::new(cx.background());
4176        fs.insert_tree(
4177            "/root1",
4178            json!({
4179                "one.txt": "",
4180                "two.txt": "",
4181            }),
4182        )
4183        .await;
4184        fs.insert_tree(
4185            "/root2",
4186            json!({
4187                "three.txt": "",
4188            }),
4189        )
4190        .await;
4191
4192        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4193        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4194        let workspace = window.root(cx);
4195        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4196        let worktree_id = project.read_with(cx, |project, cx| {
4197            project.worktrees(cx).next().unwrap().read(cx).id()
4198        });
4199
4200        let item1 = window.add_view(cx, |cx| {
4201            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4202        });
4203        let item2 = window.add_view(cx, |cx| {
4204            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4205        });
4206
4207        // Add an item to an empty pane
4208        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4209        project.read_with(cx, |project, cx| {
4210            assert_eq!(
4211                project.active_entry(),
4212                project
4213                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4214                    .map(|e| e.id)
4215            );
4216        });
4217        assert_eq!(
4218            cx.current_window_title(window.window_id()).as_deref(),
4219            Some("one.txt β€” root1")
4220        );
4221
4222        // Add a second item to a non-empty pane
4223        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4224        assert_eq!(
4225            cx.current_window_title(window.window_id()).as_deref(),
4226            Some("two.txt β€” root1")
4227        );
4228        project.read_with(cx, |project, cx| {
4229            assert_eq!(
4230                project.active_entry(),
4231                project
4232                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4233                    .map(|e| e.id)
4234            );
4235        });
4236
4237        // Close the active item
4238        pane.update(cx, |pane, cx| {
4239            pane.close_active_item(&Default::default(), cx).unwrap()
4240        })
4241        .await
4242        .unwrap();
4243        assert_eq!(
4244            cx.current_window_title(window.window_id()).as_deref(),
4245            Some("one.txt β€” root1")
4246        );
4247        project.read_with(cx, |project, cx| {
4248            assert_eq!(
4249                project.active_entry(),
4250                project
4251                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4252                    .map(|e| e.id)
4253            );
4254        });
4255
4256        // Add a project folder
4257        project
4258            .update(cx, |project, cx| {
4259                project.find_or_create_local_worktree("/root2", true, cx)
4260            })
4261            .await
4262            .unwrap();
4263        assert_eq!(
4264            cx.current_window_title(window.window_id()).as_deref(),
4265            Some("one.txt β€” root1, root2")
4266        );
4267
4268        // Remove a project folder
4269        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4270        assert_eq!(
4271            cx.current_window_title(window.window_id()).as_deref(),
4272            Some("one.txt β€” root2")
4273        );
4274    }
4275
4276    #[gpui::test]
4277    async fn test_close_window(cx: &mut TestAppContext) {
4278        init_test(cx);
4279
4280        let fs = FakeFs::new(cx.background());
4281        fs.insert_tree("/root", json!({ "one": "" })).await;
4282
4283        let project = Project::test(fs, ["root".as_ref()], cx).await;
4284        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4285        let workspace = window.root(cx);
4286
4287        // When there are no dirty items, there's nothing to do.
4288        let item1 = window.add_view(cx, |_| TestItem::new());
4289        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4290        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4291        assert!(task.await.unwrap());
4292
4293        // When there are dirty untitled items, prompt to save each one. If the user
4294        // cancels any prompt, then abort.
4295        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4296        let item3 = window.add_view(cx, |cx| {
4297            TestItem::new()
4298                .with_dirty(true)
4299                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4300        });
4301        workspace.update(cx, |w, cx| {
4302            w.add_item(Box::new(item2.clone()), cx);
4303            w.add_item(Box::new(item3.clone()), cx);
4304        });
4305        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4306        cx.foreground().run_until_parked();
4307        cx.simulate_prompt_answer(window.window_id(), 2 /* cancel */);
4308        cx.foreground().run_until_parked();
4309        assert!(!cx.has_pending_prompt(window.window_id()));
4310        assert!(!task.await.unwrap());
4311    }
4312
4313    #[gpui::test]
4314    async fn test_close_pane_items(cx: &mut TestAppContext) {
4315        init_test(cx);
4316
4317        let fs = FakeFs::new(cx.background());
4318
4319        let project = Project::test(fs, None, cx).await;
4320        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4321        let workspace = window.root(cx);
4322
4323        let item1 = window.add_view(cx, |cx| {
4324            TestItem::new()
4325                .with_dirty(true)
4326                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4327        });
4328        let item2 = window.add_view(cx, |cx| {
4329            TestItem::new()
4330                .with_dirty(true)
4331                .with_conflict(true)
4332                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4333        });
4334        let item3 = window.add_view(cx, |cx| {
4335            TestItem::new()
4336                .with_dirty(true)
4337                .with_conflict(true)
4338                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4339        });
4340        let item4 = window.add_view(cx, |cx| {
4341            TestItem::new()
4342                .with_dirty(true)
4343                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4344        });
4345        let pane = workspace.update(cx, |workspace, cx| {
4346            workspace.add_item(Box::new(item1.clone()), cx);
4347            workspace.add_item(Box::new(item2.clone()), cx);
4348            workspace.add_item(Box::new(item3.clone()), cx);
4349            workspace.add_item(Box::new(item4.clone()), cx);
4350            workspace.active_pane().clone()
4351        });
4352
4353        let close_items = pane.update(cx, |pane, cx| {
4354            pane.activate_item(1, true, true, cx);
4355            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4356            let item1_id = item1.id();
4357            let item3_id = item3.id();
4358            let item4_id = item4.id();
4359            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
4360        });
4361        cx.foreground().run_until_parked();
4362
4363        // There's a prompt to save item 1.
4364        pane.read_with(cx, |pane, _| {
4365            assert_eq!(pane.items_len(), 4);
4366            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4367        });
4368        assert!(cx.has_pending_prompt(window.window_id()));
4369
4370        // Confirm saving item 1.
4371        cx.simulate_prompt_answer(window.window_id(), 0);
4372        cx.foreground().run_until_parked();
4373
4374        // Item 1 is saved. There's a prompt to save item 3.
4375        pane.read_with(cx, |pane, cx| {
4376            assert_eq!(item1.read(cx).save_count, 1);
4377            assert_eq!(item1.read(cx).save_as_count, 0);
4378            assert_eq!(item1.read(cx).reload_count, 0);
4379            assert_eq!(pane.items_len(), 3);
4380            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4381        });
4382        assert!(cx.has_pending_prompt(window.window_id()));
4383
4384        // Cancel saving item 3.
4385        cx.simulate_prompt_answer(window.window_id(), 1);
4386        cx.foreground().run_until_parked();
4387
4388        // Item 3 is reloaded. There's a prompt to save item 4.
4389        pane.read_with(cx, |pane, cx| {
4390            assert_eq!(item3.read(cx).save_count, 0);
4391            assert_eq!(item3.read(cx).save_as_count, 0);
4392            assert_eq!(item3.read(cx).reload_count, 1);
4393            assert_eq!(pane.items_len(), 2);
4394            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4395        });
4396        assert!(cx.has_pending_prompt(window.window_id()));
4397
4398        // Confirm saving item 4.
4399        cx.simulate_prompt_answer(window.window_id(), 0);
4400        cx.foreground().run_until_parked();
4401
4402        // There's a prompt for a path for item 4.
4403        cx.simulate_new_path_selection(|_| Some(Default::default()));
4404        close_items.await.unwrap();
4405
4406        // The requested items are closed.
4407        pane.read_with(cx, |pane, cx| {
4408            assert_eq!(item4.read(cx).save_count, 0);
4409            assert_eq!(item4.read(cx).save_as_count, 1);
4410            assert_eq!(item4.read(cx).reload_count, 0);
4411            assert_eq!(pane.items_len(), 1);
4412            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4413        });
4414    }
4415
4416    #[gpui::test]
4417    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4418        init_test(cx);
4419
4420        let fs = FakeFs::new(cx.background());
4421
4422        let project = Project::test(fs, [], cx).await;
4423        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4424        let workspace = window.root(cx);
4425
4426        // Create several workspace items with single project entries, and two
4427        // workspace items with multiple project entries.
4428        let single_entry_items = (0..=4)
4429            .map(|project_entry_id| {
4430                window.add_view(cx, |cx| {
4431                    TestItem::new()
4432                        .with_dirty(true)
4433                        .with_project_items(&[TestProjectItem::new(
4434                            project_entry_id,
4435                            &format!("{project_entry_id}.txt"),
4436                            cx,
4437                        )])
4438                })
4439            })
4440            .collect::<Vec<_>>();
4441        let item_2_3 = window.add_view(cx, |cx| {
4442            TestItem::new()
4443                .with_dirty(true)
4444                .with_singleton(false)
4445                .with_project_items(&[
4446                    single_entry_items[2].read(cx).project_items[0].clone(),
4447                    single_entry_items[3].read(cx).project_items[0].clone(),
4448                ])
4449        });
4450        let item_3_4 = window.add_view(cx, |cx| {
4451            TestItem::new()
4452                .with_dirty(true)
4453                .with_singleton(false)
4454                .with_project_items(&[
4455                    single_entry_items[3].read(cx).project_items[0].clone(),
4456                    single_entry_items[4].read(cx).project_items[0].clone(),
4457                ])
4458        });
4459
4460        // Create two panes that contain the following project entries:
4461        //   left pane:
4462        //     multi-entry items:   (2, 3)
4463        //     single-entry items:  0, 1, 2, 3, 4
4464        //   right pane:
4465        //     single-entry items:  1
4466        //     multi-entry items:   (3, 4)
4467        let left_pane = workspace.update(cx, |workspace, cx| {
4468            let left_pane = workspace.active_pane().clone();
4469            workspace.add_item(Box::new(item_2_3.clone()), cx);
4470            for item in single_entry_items {
4471                workspace.add_item(Box::new(item), cx);
4472            }
4473            left_pane.update(cx, |pane, cx| {
4474                pane.activate_item(2, true, true, cx);
4475            });
4476
4477            workspace
4478                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4479                .unwrap();
4480
4481            left_pane
4482        });
4483
4484        //Need to cause an effect flush in order to respect new focus
4485        workspace.update(cx, |workspace, cx| {
4486            workspace.add_item(Box::new(item_3_4.clone()), cx);
4487            cx.focus(&left_pane);
4488        });
4489
4490        // When closing all of the items in the left pane, we should be prompted twice:
4491        // once for project entry 0, and once for project entry 2. After those two
4492        // prompts, the task should complete.
4493
4494        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
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(0)]
4500            );
4501        });
4502        cx.simulate_prompt_answer(window.window_id(), 0);
4503
4504        cx.foreground().run_until_parked();
4505        left_pane.read_with(cx, |pane, cx| {
4506            assert_eq!(
4507                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4508                &[ProjectEntryId::from_proto(2)]
4509            );
4510        });
4511        cx.simulate_prompt_answer(window.window_id(), 0);
4512
4513        cx.foreground().run_until_parked();
4514        close.await.unwrap();
4515        left_pane.read_with(cx, |pane, _| {
4516            assert_eq!(pane.items_len(), 0);
4517        });
4518    }
4519
4520    #[gpui::test]
4521    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4522        init_test(cx);
4523
4524        let fs = FakeFs::new(cx.background());
4525
4526        let project = Project::test(fs, [], cx).await;
4527        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4528        let workspace = window.root(cx);
4529        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4530
4531        let item = window.add_view(cx, |cx| {
4532            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4533        });
4534        let item_id = item.id();
4535        workspace.update(cx, |workspace, cx| {
4536            workspace.add_item(Box::new(item.clone()), cx);
4537        });
4538
4539        // Autosave on window change.
4540        item.update(cx, |item, cx| {
4541            cx.update_global(|settings: &mut SettingsStore, cx| {
4542                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4543                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4544                })
4545            });
4546            item.is_dirty = true;
4547        });
4548
4549        // Deactivating the window saves the file.
4550        cx.simulate_window_activation(None);
4551        deterministic.run_until_parked();
4552        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4553
4554        // Autosave on focus change.
4555        item.update(cx, |item, cx| {
4556            cx.focus_self();
4557            cx.update_global(|settings: &mut SettingsStore, cx| {
4558                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4559                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4560                })
4561            });
4562            item.is_dirty = true;
4563        });
4564
4565        // Blurring the item saves the file.
4566        item.update(cx, |_, cx| cx.blur());
4567        deterministic.run_until_parked();
4568        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4569
4570        // Deactivating the window still saves the file.
4571        cx.simulate_window_activation(Some(window.window_id()));
4572        item.update(cx, |item, cx| {
4573            cx.focus_self();
4574            item.is_dirty = true;
4575        });
4576        cx.simulate_window_activation(None);
4577
4578        deterministic.run_until_parked();
4579        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4580
4581        // Autosave after delay.
4582        item.update(cx, |item, cx| {
4583            cx.update_global(|settings: &mut SettingsStore, cx| {
4584                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4585                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4586                })
4587            });
4588            item.is_dirty = true;
4589            cx.emit(TestItemEvent::Edit);
4590        });
4591
4592        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4593        deterministic.advance_clock(Duration::from_millis(250));
4594        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4595
4596        // After delay expires, the file is saved.
4597        deterministic.advance_clock(Duration::from_millis(250));
4598        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4599
4600        // Autosave on focus change, ensuring closing the tab counts as such.
4601        item.update(cx, |item, cx| {
4602            cx.update_global(|settings: &mut SettingsStore, cx| {
4603                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4604                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4605                })
4606            });
4607            item.is_dirty = true;
4608        });
4609
4610        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
4611            .await
4612            .unwrap();
4613        assert!(!cx.has_pending_prompt(window.window_id()));
4614        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4615
4616        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4617        workspace.update(cx, |workspace, cx| {
4618            workspace.add_item(Box::new(item.clone()), cx);
4619        });
4620        item.update(cx, |item, cx| {
4621            item.project_items[0].update(cx, |item, _| {
4622                item.entry_id = None;
4623            });
4624            item.is_dirty = true;
4625            cx.blur();
4626        });
4627        deterministic.run_until_parked();
4628        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4629
4630        // Ensure autosave is prevented for deleted files also when closing the buffer.
4631        let _close_items =
4632            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
4633        deterministic.run_until_parked();
4634        assert!(cx.has_pending_prompt(window.window_id()));
4635        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4636    }
4637
4638    #[gpui::test]
4639    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4640        init_test(cx);
4641
4642        let fs = FakeFs::new(cx.background());
4643
4644        let project = Project::test(fs, [], cx).await;
4645        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4646        let workspace = window.root(cx);
4647
4648        let item = window.add_view(cx, |cx| {
4649            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4650        });
4651        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4652        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4653        let toolbar_notify_count = Rc::new(RefCell::new(0));
4654
4655        workspace.update(cx, |workspace, cx| {
4656            workspace.add_item(Box::new(item.clone()), cx);
4657            let toolbar_notification_count = toolbar_notify_count.clone();
4658            cx.observe(&toolbar, move |_, _, _| {
4659                *toolbar_notification_count.borrow_mut() += 1
4660            })
4661            .detach();
4662        });
4663
4664        pane.read_with(cx, |pane, _| {
4665            assert!(!pane.can_navigate_backward());
4666            assert!(!pane.can_navigate_forward());
4667        });
4668
4669        item.update(cx, |item, cx| {
4670            item.set_state("one".to_string(), cx);
4671        });
4672
4673        // Toolbar must be notified to re-render the navigation buttons
4674        assert_eq!(*toolbar_notify_count.borrow(), 1);
4675
4676        pane.read_with(cx, |pane, _| {
4677            assert!(pane.can_navigate_backward());
4678            assert!(!pane.can_navigate_forward());
4679        });
4680
4681        workspace
4682            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4683            .await
4684            .unwrap();
4685
4686        assert_eq!(*toolbar_notify_count.borrow(), 3);
4687        pane.read_with(cx, |pane, _| {
4688            assert!(!pane.can_navigate_backward());
4689            assert!(pane.can_navigate_forward());
4690        });
4691    }
4692
4693    #[gpui::test]
4694    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4695        init_test(cx);
4696        let fs = FakeFs::new(cx.background());
4697
4698        let project = Project::test(fs, [], cx).await;
4699        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4700        let workspace = window.root(cx);
4701
4702        let panel = workspace.update(cx, |workspace, cx| {
4703            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4704            workspace.add_panel(panel.clone(), cx);
4705
4706            workspace
4707                .right_dock()
4708                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4709
4710            panel
4711        });
4712
4713        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4714        pane.update(cx, |pane, cx| {
4715            let item = cx.add_view(|_| TestItem::new());
4716            pane.add_item(Box::new(item), true, true, None, cx);
4717        });
4718
4719        // Transfer focus from center to panel
4720        workspace.update(cx, |workspace, cx| {
4721            workspace.toggle_panel_focus::<TestPanel>(cx);
4722        });
4723
4724        workspace.read_with(cx, |workspace, cx| {
4725            assert!(workspace.right_dock().read(cx).is_open());
4726            assert!(!panel.is_zoomed(cx));
4727            assert!(panel.has_focus(cx));
4728        });
4729
4730        // Transfer focus from panel to center
4731        workspace.update(cx, |workspace, cx| {
4732            workspace.toggle_panel_focus::<TestPanel>(cx);
4733        });
4734
4735        workspace.read_with(cx, |workspace, cx| {
4736            assert!(workspace.right_dock().read(cx).is_open());
4737            assert!(!panel.is_zoomed(cx));
4738            assert!(!panel.has_focus(cx));
4739        });
4740
4741        // Close the dock
4742        workspace.update(cx, |workspace, cx| {
4743            workspace.toggle_dock(DockPosition::Right, cx);
4744        });
4745
4746        workspace.read_with(cx, |workspace, cx| {
4747            assert!(!workspace.right_dock().read(cx).is_open());
4748            assert!(!panel.is_zoomed(cx));
4749            assert!(!panel.has_focus(cx));
4750        });
4751
4752        // Open the dock
4753        workspace.update(cx, |workspace, cx| {
4754            workspace.toggle_dock(DockPosition::Right, cx);
4755        });
4756
4757        workspace.read_with(cx, |workspace, cx| {
4758            assert!(workspace.right_dock().read(cx).is_open());
4759            assert!(!panel.is_zoomed(cx));
4760            assert!(panel.has_focus(cx));
4761        });
4762
4763        // Focus and zoom panel
4764        panel.update(cx, |panel, cx| {
4765            cx.focus_self();
4766            panel.set_zoomed(true, cx)
4767        });
4768
4769        workspace.read_with(cx, |workspace, cx| {
4770            assert!(workspace.right_dock().read(cx).is_open());
4771            assert!(panel.is_zoomed(cx));
4772            assert!(panel.has_focus(cx));
4773        });
4774
4775        // Transfer focus to the center closes the dock
4776        workspace.update(cx, |workspace, cx| {
4777            workspace.toggle_panel_focus::<TestPanel>(cx);
4778        });
4779
4780        workspace.read_with(cx, |workspace, cx| {
4781            assert!(!workspace.right_dock().read(cx).is_open());
4782            assert!(panel.is_zoomed(cx));
4783            assert!(!panel.has_focus(cx));
4784        });
4785
4786        // Transferring focus back to the panel keeps it zoomed
4787        workspace.update(cx, |workspace, cx| {
4788            workspace.toggle_panel_focus::<TestPanel>(cx);
4789        });
4790
4791        workspace.read_with(cx, |workspace, cx| {
4792            assert!(workspace.right_dock().read(cx).is_open());
4793            assert!(panel.is_zoomed(cx));
4794            assert!(panel.has_focus(cx));
4795        });
4796
4797        // Close the dock while it is zoomed
4798        workspace.update(cx, |workspace, cx| {
4799            workspace.toggle_dock(DockPosition::Right, cx)
4800        });
4801
4802        workspace.read_with(cx, |workspace, cx| {
4803            assert!(!workspace.right_dock().read(cx).is_open());
4804            assert!(panel.is_zoomed(cx));
4805            assert!(workspace.zoomed.is_none());
4806            assert!(!panel.has_focus(cx));
4807        });
4808
4809        // Opening the dock, when it's zoomed, retains focus
4810        workspace.update(cx, |workspace, cx| {
4811            workspace.toggle_dock(DockPosition::Right, cx)
4812        });
4813
4814        workspace.read_with(cx, |workspace, cx| {
4815            assert!(workspace.right_dock().read(cx).is_open());
4816            assert!(panel.is_zoomed(cx));
4817            assert!(workspace.zoomed.is_some());
4818            assert!(panel.has_focus(cx));
4819        });
4820
4821        // Unzoom and close the panel, zoom the active pane.
4822        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4823        workspace.update(cx, |workspace, cx| {
4824            workspace.toggle_dock(DockPosition::Right, cx)
4825        });
4826        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4827
4828        // Opening a dock unzooms the pane.
4829        workspace.update(cx, |workspace, cx| {
4830            workspace.toggle_dock(DockPosition::Right, cx)
4831        });
4832        workspace.read_with(cx, |workspace, cx| {
4833            let pane = pane.read(cx);
4834            assert!(!pane.is_zoomed());
4835            assert!(!pane.has_focus());
4836            assert!(workspace.right_dock().read(cx).is_open());
4837            assert!(workspace.zoomed.is_none());
4838        });
4839    }
4840
4841    #[gpui::test]
4842    async fn test_panels(cx: &mut gpui::TestAppContext) {
4843        init_test(cx);
4844        let fs = FakeFs::new(cx.background());
4845
4846        let project = Project::test(fs, [], cx).await;
4847        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4848        let workspace = window.root(cx);
4849
4850        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4851            // Add panel_1 on the left, panel_2 on the right.
4852            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4853            workspace.add_panel(panel_1.clone(), cx);
4854            workspace
4855                .left_dock()
4856                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4857            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4858            workspace.add_panel(panel_2.clone(), cx);
4859            workspace
4860                .right_dock()
4861                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4862
4863            let left_dock = workspace.left_dock();
4864            assert_eq!(
4865                left_dock.read(cx).visible_panel().unwrap().id(),
4866                panel_1.id()
4867            );
4868            assert_eq!(
4869                left_dock.read(cx).active_panel_size(cx).unwrap(),
4870                panel_1.size(cx)
4871            );
4872
4873            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4874            assert_eq!(
4875                workspace
4876                    .right_dock()
4877                    .read(cx)
4878                    .visible_panel()
4879                    .unwrap()
4880                    .id(),
4881                panel_2.id()
4882            );
4883
4884            (panel_1, panel_2)
4885        });
4886
4887        // Move panel_1 to the right
4888        panel_1.update(cx, |panel_1, cx| {
4889            panel_1.set_position(DockPosition::Right, cx)
4890        });
4891
4892        workspace.update(cx, |workspace, cx| {
4893            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4894            // Since it was the only panel on the left, the left dock should now be closed.
4895            assert!(!workspace.left_dock().read(cx).is_open());
4896            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4897            let right_dock = workspace.right_dock();
4898            assert_eq!(
4899                right_dock.read(cx).visible_panel().unwrap().id(),
4900                panel_1.id()
4901            );
4902            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4903
4904            // Now we move panel_2Β to the left
4905            panel_2.set_position(DockPosition::Left, cx);
4906        });
4907
4908        workspace.update(cx, |workspace, cx| {
4909            // Since panel_2 was not visible on the right, we don't open the left dock.
4910            assert!(!workspace.left_dock().read(cx).is_open());
4911            // And the right dock is unaffected in it's displaying of panel_1
4912            assert!(workspace.right_dock().read(cx).is_open());
4913            assert_eq!(
4914                workspace
4915                    .right_dock()
4916                    .read(cx)
4917                    .visible_panel()
4918                    .unwrap()
4919                    .id(),
4920                panel_1.id()
4921            );
4922        });
4923
4924        // Move panel_1 back to the left
4925        panel_1.update(cx, |panel_1, cx| {
4926            panel_1.set_position(DockPosition::Left, cx)
4927        });
4928
4929        workspace.update(cx, |workspace, cx| {
4930            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4931            let left_dock = workspace.left_dock();
4932            assert!(left_dock.read(cx).is_open());
4933            assert_eq!(
4934                left_dock.read(cx).visible_panel().unwrap().id(),
4935                panel_1.id()
4936            );
4937            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4938            // And right the dock should be closed as it no longer has any panels.
4939            assert!(!workspace.right_dock().read(cx).is_open());
4940
4941            // Now we move panel_1 to the bottom
4942            panel_1.set_position(DockPosition::Bottom, cx);
4943        });
4944
4945        workspace.update(cx, |workspace, cx| {
4946            // Since panel_1 was visible on the left, we close the left dock.
4947            assert!(!workspace.left_dock().read(cx).is_open());
4948            // The bottom dock is sized based on the panel's default size,
4949            // since the panel orientation changed from vertical to horizontal.
4950            let bottom_dock = workspace.bottom_dock();
4951            assert_eq!(
4952                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4953                panel_1.size(cx),
4954            );
4955            // Close bottom dock and move panel_1 back to the left.
4956            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4957            panel_1.set_position(DockPosition::Left, cx);
4958        });
4959
4960        // Emit activated event on panel 1
4961        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4962
4963        // Now the left dock is open and panel_1 is active and focused.
4964        workspace.read_with(cx, |workspace, cx| {
4965            let left_dock = workspace.left_dock();
4966            assert!(left_dock.read(cx).is_open());
4967            assert_eq!(
4968                left_dock.read(cx).visible_panel().unwrap().id(),
4969                panel_1.id()
4970            );
4971            assert!(panel_1.is_focused(cx));
4972        });
4973
4974        // Emit closed event on panel 2, which is not active
4975        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4976
4977        // Wo don't close the left dock, because panel_2 wasn't the active panel
4978        workspace.read_with(cx, |workspace, cx| {
4979            let left_dock = workspace.left_dock();
4980            assert!(left_dock.read(cx).is_open());
4981            assert_eq!(
4982                left_dock.read(cx).visible_panel().unwrap().id(),
4983                panel_1.id()
4984            );
4985        });
4986
4987        // Emitting a ZoomIn event shows the panel as zoomed.
4988        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4989        workspace.read_with(cx, |workspace, _| {
4990            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4991            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
4992        });
4993
4994        // Move panel to another dock while it is zoomed
4995        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
4996        workspace.read_with(cx, |workspace, _| {
4997            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4998            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4999        });
5000
5001        // If focus is transferred to another view that's not a panel or another pane, we still show
5002        // the panel as zoomed.
5003        let focus_receiver = window.add_view(cx, |_| EmptyView);
5004        focus_receiver.update(cx, |_, cx| cx.focus_self());
5005        workspace.read_with(cx, |workspace, _| {
5006            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5007            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5008        });
5009
5010        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5011        workspace.update(cx, |_, cx| cx.focus_self());
5012        workspace.read_with(cx, |workspace, _| {
5013            assert_eq!(workspace.zoomed, None);
5014            assert_eq!(workspace.zoomed_position, None);
5015        });
5016
5017        // If focus is transferred again to another view that's not a panel or a pane, we won't
5018        // show the panel as zoomed because it wasn't zoomed before.
5019        focus_receiver.update(cx, |_, cx| cx.focus_self());
5020        workspace.read_with(cx, |workspace, _| {
5021            assert_eq!(workspace.zoomed, None);
5022            assert_eq!(workspace.zoomed_position, None);
5023        });
5024
5025        // When focus is transferred back to the panel, it is zoomed again.
5026        panel_1.update(cx, |_, cx| cx.focus_self());
5027        workspace.read_with(cx, |workspace, _| {
5028            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5029            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5030        });
5031
5032        // Emitting a ZoomOut event unzooms the panel.
5033        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5034        workspace.read_with(cx, |workspace, _| {
5035            assert_eq!(workspace.zoomed, None);
5036            assert_eq!(workspace.zoomed_position, None);
5037        });
5038
5039        // Emit closed event on panel 1, which is active
5040        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5041
5042        // Now the left dock is closed, because panel_1 was the active panel
5043        workspace.read_with(cx, |workspace, cx| {
5044            let right_dock = workspace.right_dock();
5045            assert!(!right_dock.read(cx).is_open());
5046        });
5047    }
5048
5049    pub fn init_test(cx: &mut TestAppContext) {
5050        cx.foreground().forbid_parking();
5051        cx.update(|cx| {
5052            cx.set_global(SettingsStore::test(cx));
5053            theme::init((), cx);
5054            language::init(cx);
5055            crate::init_settings(cx);
5056            Project::init_settings(cx);
5057        });
5058    }
5059}