use std::rc::{Rc, Weak};
use gpui::{App, Entity, Global, OwnedMenu};
use crate::text::TextViewState;
pub struct DeferredPopover(#[allow(dead_code)] Rc<()>);
pub struct GlobalState {
app_menus: Vec<OwnedMenu>,
deferred_popovers: Vec<Weak<()>>,
suppress_text_selection: bool,
pub(crate) text_view_state_stack: Vec<Entity<TextViewState>>,
selection_document_order: u64,
}
impl Global for GlobalState {}
impl GlobalState {
fn new() -> Self {
Self {
app_menus: Vec::new(),
deferred_popovers: Vec::new(),
suppress_text_selection: false,
text_view_state_stack: Vec::new(),
selection_document_order: 1,
}
}
#[doc(hidden)]
pub fn init(cx: &mut App) {
if !cx.has_global::<Self>() {
cx.set_global(Self::new());
}
}
pub fn suppress_text_selection(cx: &mut App) {
Self::global_mut(cx).suppress_text_selection = true;
}
#[doc(hidden)]
pub fn reset_text_selection_suppression(cx: &mut App) {
Self::global_mut(cx).suppress_text_selection = false;
}
#[doc(hidden)]
pub fn is_text_selection_suppressed(cx: &App) -> bool {
Self::global(cx).suppress_text_selection
}
pub fn global(cx: &App) -> &Self {
cx.global::<Self>()
}
pub fn global_mut(cx: &mut App) -> &mut Self {
cx.global_mut::<Self>()
}
pub(crate) fn text_view_state(&self) -> Option<&Entity<TextViewState>> {
self.text_view_state_stack.last()
}
#[doc(hidden)]
pub fn begin_selection_frame(&mut self) {
self.selection_document_order = 1;
}
pub(crate) fn next_selection_document_order(&mut self) -> u64 {
let order = self.selection_document_order;
self.selection_document_order = self.selection_document_order.wrapping_add(1);
order
}
pub fn app_menus(&self) -> &[OwnedMenu] {
&self.app_menus
}
pub fn set_app_menus(&mut self, menus: Vec<OwnedMenu>) {
self.app_menus = menus;
}
pub fn is_in_deferred_context(cx: &App) -> bool {
Self::global(cx)
.deferred_popovers
.iter()
.any(|popover| popover.strong_count() > 0)
}
pub fn register_deferred_popover(cx: &mut App) -> DeferredPopover {
let token = Rc::new(());
let state = Self::global_mut(cx);
state
.deferred_popovers
.retain(|popover| popover.strong_count() > 0);
state.deferred_popovers.push(Rc::downgrade(&token));
DeferredPopover(token)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[gpui::test]
fn initialization_is_idempotent_and_suppression_can_be_reset(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
GlobalState::init(cx);
GlobalState::suppress_text_selection(cx);
GlobalState::init(cx);
assert!(GlobalState::is_text_selection_suppressed(cx));
GlobalState::reset_text_selection_suppression(cx);
assert!(!GlobalState::is_text_selection_suppressed(cx));
assert!(!GlobalState::is_in_deferred_context(cx));
let popover = GlobalState::register_deferred_popover(cx);
assert!(GlobalState::is_in_deferred_context(cx));
drop(popover);
assert!(!GlobalState::is_in_deferred_context(cx));
});
}
#[gpui::test]
fn a_dropped_registration_closes_the_deferred_context(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
GlobalState::init(cx);
let outer = GlobalState::register_deferred_popover(cx);
{
let _inner = GlobalState::register_deferred_popover(cx);
assert!(GlobalState::is_in_deferred_context(cx));
}
assert!(GlobalState::is_in_deferred_context(cx));
drop(outer);
assert!(!GlobalState::is_in_deferred_context(cx));
let popover = GlobalState::register_deferred_popover(cx);
assert_eq!(GlobalState::global(cx).deferred_popovers.len(), 1);
drop(popover);
assert!(!GlobalState::is_in_deferred_context(cx));
});
}
}