2026-04-20 15:13:26 +01:00
|
|
|
use gpui::{AppContext, BorrowAppContext};
|
|
|
|
|
use std::any::Any;
|
|
|
|
|
|
2026-04-20 22:54:31 +01:00
|
|
|
pub trait QueryFn<C>: Clone + 'static
|
|
|
|
|
where
|
2026-04-21 11:50:04 +01:00
|
|
|
C: Context,
|
2026-04-20 22:54:31 +01:00
|
|
|
{
|
2026-04-20 15:13:26 +01:00
|
|
|
type Data: 'static;
|
|
|
|
|
type Error: 'static;
|
|
|
|
|
|
|
|
|
|
fn key(&self) -> &'static str;
|
|
|
|
|
|
2026-04-20 22:54:31 +01:00
|
|
|
async fn run(&self, c: &C) -> Result<Self::Data, Self::Error>;
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub enum QueryStatus<'a, Data, Error> {
|
|
|
|
|
Loading,
|
|
|
|
|
Loaded(&'a Data),
|
|
|
|
|
Err(&'a Error),
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 22:54:31 +01:00
|
|
|
pub fn use_query<F, T, C>(query_fn: F, cx: &mut gpui::Context<T>)
|
2026-04-20 15:13:26 +01:00
|
|
|
where
|
2026-04-20 22:54:31 +01:00
|
|
|
C: Context + 'static,
|
|
|
|
|
F: QueryFn<C>,
|
2026-04-20 15:13:26 +01:00
|
|
|
T: 'static,
|
2026-04-20 22:54:31 +01:00
|
|
|
Store<C>: gpui::Global,
|
2026-04-20 15:13:26 +01:00
|
|
|
{
|
2026-04-20 22:54:31 +01:00
|
|
|
let ent = cx.update_global::<Store<C>, _>(|store, cx| store.ensure_query_data(&query_fn, cx));
|
2026-04-20 15:13:26 +01:00
|
|
|
|
|
|
|
|
cx.observe(&ent, |_, _, cx| {
|
|
|
|
|
cx.notify();
|
|
|
|
|
})
|
|
|
|
|
.detach();
|
2026-04-20 22:54:31 +01:00
|
|
|
|
|
|
|
|
let query_context = cx.global::<Store<C>>().query_context.clone();
|
|
|
|
|
|
|
|
|
|
cx.observe(&query_context, move |_, _, cx| {
|
|
|
|
|
cx.update_global::<Store<C>, _>(|store, cx| {
|
|
|
|
|
store.invalidate_query(&query_fn, cx);
|
|
|
|
|
store.ensure_query_data(&query_fn, cx);
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.detach();
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 11:50:04 +01:00
|
|
|
pub fn read_query<'a, F, T, C>(
|
|
|
|
|
query_fn: F,
|
|
|
|
|
cx: &'a gpui::Context<T>,
|
|
|
|
|
) -> QueryStatus<'a, F::Data, F::Error>
|
|
|
|
|
where
|
|
|
|
|
C: Context + 'static,
|
|
|
|
|
F: QueryFn<C>,
|
|
|
|
|
T: 'static,
|
|
|
|
|
{
|
|
|
|
|
let store = cx.global::<Store<C>>();
|
|
|
|
|
let Some(ent) = store.query_data.get(query_fn.key()) else {
|
|
|
|
|
return QueryStatus::Loading;
|
|
|
|
|
};
|
|
|
|
|
let QueryState { data } = ent.read(cx);
|
|
|
|
|
match data {
|
|
|
|
|
QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading,
|
|
|
|
|
QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::<F::Data>().unwrap()),
|
|
|
|
|
QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::<F::Error>().unwrap()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 15:13:26 +01:00
|
|
|
// ================= Store ==================
|
|
|
|
|
|
|
|
|
|
pub(crate) struct QueryState {
|
|
|
|
|
data: QueryData,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) enum QueryData {
|
|
|
|
|
Pending,
|
|
|
|
|
Loading,
|
2026-04-20 22:54:31 +01:00
|
|
|
Stale,
|
2026-04-20 15:13:26 +01:00
|
|
|
Some(Box<dyn Any>),
|
|
|
|
|
Err(Box<dyn Any>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) type Entity = gpui::Entity<QueryState>;
|
|
|
|
|
|
2026-04-21 11:50:04 +01:00
|
|
|
pub(crate) trait Context: Clone {}
|
2026-04-20 22:54:31 +01:00
|
|
|
|
|
|
|
|
pub struct Store<C>
|
|
|
|
|
where
|
|
|
|
|
C: Context,
|
|
|
|
|
{
|
2026-04-20 15:13:26 +01:00
|
|
|
query_data: std::collections::HashMap<String, Entity>,
|
2026-04-20 22:54:31 +01:00
|
|
|
query_context: gpui::Entity<C>,
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-20 22:54:31 +01:00
|
|
|
impl<C> Store<C>
|
|
|
|
|
where
|
|
|
|
|
C: Context + 'static,
|
|
|
|
|
{
|
2026-04-21 11:50:04 +01:00
|
|
|
pub fn new(ctx: C, cx: &mut gpui::App) -> Self {
|
2026-04-20 15:13:26 +01:00
|
|
|
Self {
|
|
|
|
|
query_data: std::collections::HashMap::new(),
|
2026-04-20 22:54:31 +01:00
|
|
|
query_context: cx.new(|_| ctx),
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 22:54:31 +01:00
|
|
|
fn entity_for<Q, T>(&mut self, query: &Q, cx: &mut gpui::Context<T>) -> Entity
|
2026-04-20 15:13:26 +01:00
|
|
|
where
|
2026-04-20 22:54:31 +01:00
|
|
|
Q: QueryFn<C>,
|
2026-04-20 15:13:26 +01:00
|
|
|
T: 'static,
|
|
|
|
|
{
|
|
|
|
|
self.query_data
|
|
|
|
|
.entry(query.key().into())
|
|
|
|
|
.or_insert_with(|| {
|
|
|
|
|
cx.new(|_| QueryState {
|
|
|
|
|
data: QueryData::Pending,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.clone()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 22:54:31 +01:00
|
|
|
fn ensure_query_data<Q, T>(&mut self, query: &Q, cx: &mut gpui::Context<T>) -> Entity
|
|
|
|
|
where
|
|
|
|
|
T: 'static,
|
|
|
|
|
Q: QueryFn<C>,
|
|
|
|
|
{
|
|
|
|
|
let entity = self.entity_for(query, cx);
|
|
|
|
|
|
|
|
|
|
let should_execute = entity.read_with(cx, |state, _| {
|
|
|
|
|
matches!(state.data, QueryData::Pending | QueryData::Stale)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if should_execute {
|
|
|
|
|
self.execute_query(query, cx).detach();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entity
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 15:13:26 +01:00
|
|
|
fn execute_query<Q, T>(
|
|
|
|
|
&mut self,
|
2026-04-20 22:54:31 +01:00
|
|
|
query: &Q,
|
2026-04-20 15:13:26 +01:00
|
|
|
cx: &mut gpui::Context<T>,
|
|
|
|
|
) -> gpui::Task<anyhow::Result<()>>
|
|
|
|
|
where
|
2026-04-20 22:54:31 +01:00
|
|
|
Q: QueryFn<C>,
|
2026-04-20 15:13:26 +01:00
|
|
|
T: 'static,
|
|
|
|
|
{
|
2026-04-20 22:54:31 +01:00
|
|
|
let entity = self.entity_for(query, cx);
|
|
|
|
|
|
2026-04-20 15:13:26 +01:00
|
|
|
entity.update(cx, |state, cx| {
|
|
|
|
|
state.data = QueryData::Loading;
|
|
|
|
|
cx.notify();
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-21 11:50:04 +01:00
|
|
|
let q = query.clone();
|
|
|
|
|
let query_context = self.query_context.clone();
|
|
|
|
|
|
2026-04-20 15:13:26 +01:00
|
|
|
cx.spawn(async move |_, cx| {
|
2026-04-21 11:50:04 +01:00
|
|
|
let c = query_context.read_with(cx, |c, _| c.clone())?;
|
|
|
|
|
let result = q.run(&c).await;
|
2026-04-20 15:13:26 +01:00
|
|
|
|
|
|
|
|
entity.update(cx, |state, cx| {
|
|
|
|
|
state.data = match result {
|
|
|
|
|
Ok(data) => QueryData::Some(Box::new(data)),
|
|
|
|
|
Err(err) => QueryData::Err(Box::new(err)),
|
|
|
|
|
};
|
|
|
|
|
cx.notify();
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
anyhow::Ok(())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 22:54:31 +01:00
|
|
|
fn invalidate_query<Q, T>(&mut self, query: &Q, cx: &mut gpui::Context<T>)
|
|
|
|
|
where
|
|
|
|
|
Q: QueryFn<C>,
|
|
|
|
|
{
|
|
|
|
|
if let Some(entity) = self.query_data.get(query.key()) {
|
|
|
|
|
entity.update(cx, |query, cx| {
|
|
|
|
|
if !matches!(query.data, QueryData::Loading) {
|
|
|
|
|
query.data = QueryData::Stale;
|
|
|
|
|
cx.notify()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn read_query<'a, Q, E>(
|
|
|
|
|
&self,
|
|
|
|
|
query_fn: Q,
|
|
|
|
|
cx: &'a gpui::Context<E>,
|
|
|
|
|
) -> QueryStatus<'a, Q::Data, Q::Error>
|
|
|
|
|
where
|
|
|
|
|
Q: QueryFn<C>,
|
|
|
|
|
{
|
2026-04-20 15:13:26 +01:00
|
|
|
let Some(ent) = self.query_data.get(query_fn.key()) else {
|
|
|
|
|
return QueryStatus::Loading;
|
|
|
|
|
};
|
|
|
|
|
let QueryState { data } = ent.read(cx);
|
|
|
|
|
match data {
|
2026-04-20 22:54:31 +01:00
|
|
|
QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading,
|
2026-04-20 15:13:26 +01:00
|
|
|
QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::<Q::Data>().unwrap()),
|
2026-04-20 22:54:31 +01:00
|
|
|
QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::<Q::Error>().unwrap()),
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-21 11:50:04 +01:00
|
|
|
|
|
|
|
|
impl<C> gpui::Global for Store<C> where C: Context + 'static {}
|