workspace.rs

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