2026-05-25 00:08:22 +01:00
|
|
|
use gpui::BorrowAppContext;
|
2026-05-06 01:42:38 +08:00
|
|
|
use std::{any::Any, borrow::Cow, collections::HashMap, marker::PhantomData, ops::Deref};
|
2026-04-20 15:13:26 +01:00
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
pub(crate) trait QueryFn: Clone + 'static {
|
2026-04-20 15:13:26 +01:00
|
|
|
type Data: 'static;
|
2026-04-24 19:22:25 +01:00
|
|
|
type Error: std::fmt::Debug + 'static;
|
2026-04-21 20:30:41 +01:00
|
|
|
type Context: Context;
|
2026-04-20 15:13:26 +01:00
|
|
|
|
2026-05-06 01:42:38 +08:00
|
|
|
fn key(&self) -> Key;
|
2026-04-20 15:13:26 +01:00
|
|
|
|
2026-04-21 20:30:41 +01:00
|
|
|
async fn run(&self, c: &Self::Context) -> Result<Self::Data, Self::Error>;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
pub(crate) trait QueryAppContext: gpui::AppContext {
|
|
|
|
|
fn ready<T>(value: T) -> Self::Result<T>;
|
|
|
|
|
|
|
|
|
|
fn map_result<T, U>(result: Self::Result<T>, f: impl FnOnce(T) -> U) -> Self::Result<U>;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 01:42:38 +08:00
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
|
|
|
pub(crate) struct Key(Cow<'static, str>);
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
pub(crate) struct Query {
|
2026-04-21 20:30:41 +01:00
|
|
|
data: QueryData,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum QueryData {
|
|
|
|
|
Pending,
|
|
|
|
|
Loading,
|
|
|
|
|
Stale,
|
|
|
|
|
Some(Box<dyn Any>),
|
|
|
|
|
Err(Box<dyn Any>),
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub enum QueryStatus<'a, Data, Error> {
|
|
|
|
|
Loading,
|
|
|
|
|
Loaded(&'a Data),
|
|
|
|
|
Err(&'a Error),
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:30:41 +01:00
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub(crate) struct Entity<F>
|
2026-04-20 15:13:26 +01:00
|
|
|
where
|
2026-04-21 20:30:41 +01:00
|
|
|
F: QueryFn,
|
|
|
|
|
{
|
|
|
|
|
raw: gpui::Entity<Query>,
|
|
|
|
|
_marker: PhantomData<fn() -> F>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn use_query<F, T>(query_fn: F, cx: &mut gpui::Context<T>) -> Entity<F>
|
|
|
|
|
where
|
|
|
|
|
F: QueryFn,
|
2026-04-20 15:13:26 +01:00
|
|
|
T: 'static,
|
2026-04-21 20:30:41 +01:00
|
|
|
Store<F::Context>: gpui::Global,
|
2026-04-20 15:13:26 +01:00
|
|
|
{
|
2026-04-22 12:41:33 +01:00
|
|
|
let ent = cx.update_global::<Store<F::Context>, _>(|store, cx| {
|
|
|
|
|
let ent = store.entity_for(&query_fn, cx);
|
|
|
|
|
store.ensure_query_data(&query_fn, cx);
|
|
|
|
|
ent
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
cx.observe(&ent, move |_, ent, cx| {
|
2026-04-22 12:41:33 +01:00
|
|
|
let query = ent.read(cx);
|
|
|
|
|
if matches!(query.data, QueryData::Stale) {
|
|
|
|
|
cx.update_global::<Store<F::Context>, _>(|store, cx| {
|
|
|
|
|
store.ensure_query_data(&query_fn, cx);
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-04-20 15:13:26 +01:00
|
|
|
cx.notify();
|
|
|
|
|
})
|
|
|
|
|
.detach();
|
2026-04-20 22:54:31 +01:00
|
|
|
|
2026-04-21 20:30:41 +01:00
|
|
|
ent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn use_lazy_query<F, T>(query_fn: F, cx: &mut gpui::Context<T>) -> Entity<F>
|
|
|
|
|
where
|
|
|
|
|
F: QueryFn,
|
|
|
|
|
T: 'static,
|
|
|
|
|
Store<F::Context>: gpui::Global,
|
|
|
|
|
{
|
|
|
|
|
let ent = cx.update_global::<Store<F::Context>, _>(|store, cx| store.entity_for(&query_fn, cx));
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
cx.observe(&ent, move |_, ent, cx| {
|
2026-04-22 12:41:33 +01:00
|
|
|
let query = ent.read(cx);
|
|
|
|
|
if matches!(query.data, QueryData::Stale) {
|
|
|
|
|
cx.update_global::<Store<F::Context>, _>(|store, cx| {
|
|
|
|
|
store.ensure_query_data(&query_fn, cx);
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-04-21 20:30:41 +01:00
|
|
|
cx.notify();
|
|
|
|
|
})
|
|
|
|
|
.detach();
|
2026-04-20 22:54:31 +01:00
|
|
|
|
2026-04-21 20:30:41 +01:00
|
|
|
ent
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
pub async fn fetch_query<F>(query_fn: F, cx: &mut gpui::AsyncApp) -> anyhow::Result<Entity<F>>
|
|
|
|
|
where
|
|
|
|
|
F: QueryFn,
|
|
|
|
|
{
|
|
|
|
|
let ent = cx.update(|cx| {
|
|
|
|
|
cx.update_global::<Store<F::Context>, _>(|store, cx| store.ensure_query_data(&query_fn, cx))
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
enum WaitState {
|
|
|
|
|
Cached,
|
|
|
|
|
Waiting {
|
|
|
|
|
rx: futures::channel::oneshot::Receiver<()>,
|
|
|
|
|
sub: gpui::Subscription,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
let wait_state = cx.update(|cx| {
|
|
|
|
|
let is_done = ent.read_with(cx, |query, _| {
|
|
|
|
|
matches!(query.data, QueryData::Some(_) | QueryData::Err(_))
|
|
|
|
|
});
|
|
|
|
|
if is_done {
|
|
|
|
|
WaitState::Cached
|
|
|
|
|
} else {
|
|
|
|
|
let (tx, rx) = futures::channel::oneshot::channel();
|
|
|
|
|
let ent = ent.clone();
|
|
|
|
|
let mut tx = Some(tx);
|
|
|
|
|
|
|
|
|
|
let sub = cx.observe(&ent, move |ent, cx| {
|
|
|
|
|
let is_done = ent.read_with(cx, |query, _| {
|
|
|
|
|
matches!(query.data, QueryData::Some(_) | QueryData::Err(_))
|
|
|
|
|
});
|
|
|
|
|
if is_done && let Some(tx) = tx.take() {
|
2026-04-25 00:49:50 +01:00
|
|
|
_ = tx.send(());
|
2026-04-24 19:22:25 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
WaitState::Waiting { rx, sub }
|
|
|
|
|
}
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
match wait_state {
|
2026-05-24 16:44:10 +01:00
|
|
|
| WaitState::Cached => {
|
|
|
|
|
return Ok(ent);
|
|
|
|
|
}
|
|
|
|
|
| WaitState::Waiting { rx, sub } => {
|
|
|
|
|
_ = sub;
|
|
|
|
|
_ = rx.await;
|
|
|
|
|
}
|
2026-04-24 19:22:25 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 12:41:33 +01:00
|
|
|
impl<F> Entity<F>
|
|
|
|
|
where
|
|
|
|
|
F: QueryFn,
|
|
|
|
|
Store<F::Context>: gpui::Global,
|
|
|
|
|
{
|
2026-04-24 19:22:25 +01:00
|
|
|
pub fn refetch<E>(&self, cx: &mut gpui::Context<E>)
|
|
|
|
|
where
|
|
|
|
|
E: 'static,
|
|
|
|
|
{
|
2026-04-22 12:41:33 +01:00
|
|
|
cx.update_global::<Store<F::Context>, _>(|store, cx| {
|
|
|
|
|
store.invalidate_query(self, cx);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
impl<F> Deref for Entity<F>
|
|
|
|
|
where
|
|
|
|
|
F: QueryFn,
|
|
|
|
|
{
|
|
|
|
|
type Target = gpui::Entity<Query>;
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
&self.raw
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn read_query<'a, F>(query: &Entity<F>, cx: &'a gpui::App) -> QueryStatus<'a, F::Data, F::Error>
|
2026-04-21 11:50:04 +01:00
|
|
|
where
|
2026-04-21 20:30:41 +01:00
|
|
|
F: QueryFn,
|
2026-04-21 11:50:04 +01:00
|
|
|
{
|
2026-04-21 20:30:41 +01:00
|
|
|
let state = query.raw.read(cx);
|
|
|
|
|
|
|
|
|
|
match &state.data {
|
2026-05-24 16:44:10 +01:00
|
|
|
| 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-21 11:50:04 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 00:08:22 +01:00
|
|
|
pub fn watch_query<E, F, H>(query: &Entity<F>, on_notify: H, cx: &mut gpui::Context<E>) -> gpui::Subscription
|
2026-05-24 16:44:10 +01:00
|
|
|
where
|
|
|
|
|
E: 'static,
|
|
|
|
|
F: QueryFn,
|
2026-05-25 00:08:22 +01:00
|
|
|
H: Fn(&mut E, &Entity<F>, &mut gpui::Context<E>) + Clone + 'static,
|
2026-05-24 16:44:10 +01:00
|
|
|
{
|
2026-05-25 00:08:22 +01:00
|
|
|
let observed_query = query.clone();
|
|
|
|
|
let sub = cx.observe(query, {
|
|
|
|
|
let on_notify = on_notify.clone();
|
|
|
|
|
move |this, _, cx| on_notify(this, &observed_query, cx)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let initial_query = query.clone();
|
|
|
|
|
cx.spawn({
|
|
|
|
|
let on_notify = on_notify.clone();
|
|
|
|
|
async move |weak, cx| {
|
|
|
|
|
let _ = weak.update(cx, |this, cx| on_notify(this, &initial_query, cx));
|
|
|
|
|
}
|
2026-05-24 16:44:10 +01:00
|
|
|
})
|
2026-05-25 00:08:22 +01:00
|
|
|
.detach();
|
|
|
|
|
|
|
|
|
|
sub
|
2026-05-24 16:44:10 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-20 15:13:26 +01:00
|
|
|
// ================= Store ==================
|
|
|
|
|
|
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-05-06 01:42:38 +08:00
|
|
|
query_data: HashMap<Key, gpui::Entity<Query>>,
|
2026-04-24 19:22:25 +01:00
|
|
|
query_context: 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-24 19:22:25 +01:00
|
|
|
pub fn new(ctx: C) -> Self {
|
2026-04-20 15:13:26 +01:00
|
|
|
Self {
|
2026-04-24 19:22:25 +01:00
|
|
|
query_context: ctx,
|
2026-04-22 12:41:33 +01:00
|
|
|
query_data: std::collections::HashMap::new(),
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
pub(crate) fn update_query_context(&mut self, f: impl FnOnce(&mut C)) {
|
|
|
|
|
f(&mut self.query_context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn entity_for<Q, CX>(&mut self, query: &Q, cx: &mut CX) -> CX::Result<Entity<Q>>
|
2026-04-20 15:13:26 +01:00
|
|
|
where
|
2026-04-21 20:30:41 +01:00
|
|
|
Q: QueryFn<Context = C>,
|
2026-04-24 19:22:25 +01:00
|
|
|
CX: QueryAppContext,
|
2026-04-20 15:13:26 +01:00
|
|
|
{
|
2026-05-06 01:42:38 +08:00
|
|
|
let query_key = query.key();
|
|
|
|
|
|
|
|
|
|
if let Some(raw) = self.query_data.get(&query_key) {
|
2026-04-24 19:22:25 +01:00
|
|
|
return CX::ready(Entity {
|
|
|
|
|
raw: raw.clone(),
|
|
|
|
|
_marker: PhantomData,
|
|
|
|
|
});
|
2026-04-21 20:30:41 +01:00
|
|
|
}
|
2026-04-24 19:22:25 +01:00
|
|
|
|
|
|
|
|
CX::map_result(
|
|
|
|
|
cx.new(|_| Query {
|
|
|
|
|
data: QueryData::Pending,
|
|
|
|
|
}),
|
|
|
|
|
|raw| {
|
2026-05-06 01:42:38 +08:00
|
|
|
self.query_data.insert(query_key, raw.clone());
|
2026-04-24 19:22:25 +01:00
|
|
|
Entity {
|
|
|
|
|
raw,
|
|
|
|
|
_marker: PhantomData,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
fn ensure_query_data<Q>(&mut self, query: &Q, cx: &mut gpui::App) -> Entity<Q>
|
2026-04-20 22:54:31 +01:00
|
|
|
where
|
2026-04-21 20:30:41 +01:00
|
|
|
Q: QueryFn<Context = C>,
|
2026-04-20 22:54:31 +01:00
|
|
|
{
|
|
|
|
|
let entity = self.entity_for(query, cx);
|
|
|
|
|
|
2026-04-21 20:30:41 +01:00
|
|
|
let should_execute = entity.raw.read_with(cx, |state, _| {
|
2026-04-20 22:54:31 +01:00
|
|
|
matches!(state.data, QueryData::Pending | QueryData::Stale)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if should_execute {
|
2026-04-24 19:22:25 +01:00
|
|
|
self.execute_query_detached(query, cx).detach();
|
2026-04-20 22:54:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entity
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
fn execute_query_detached<Q>(
|
2026-04-20 15:13:26 +01:00
|
|
|
&mut self,
|
2026-04-20 22:54:31 +01:00
|
|
|
query: &Q,
|
2026-04-24 19:22:25 +01:00
|
|
|
cx: &mut gpui::App,
|
2026-04-20 15:13:26 +01:00
|
|
|
) -> gpui::Task<anyhow::Result<()>>
|
|
|
|
|
where
|
2026-04-21 20:30:41 +01:00
|
|
|
Q: QueryFn<Context = C>,
|
2026-04-20 15:13:26 +01:00
|
|
|
{
|
2026-04-20 22:54:31 +01:00
|
|
|
let entity = self.entity_for(query, cx);
|
|
|
|
|
|
2026-04-21 20:30:41 +01:00
|
|
|
entity.raw.update(cx, |state, cx| {
|
2026-04-20 15:13:26 +01:00
|
|
|
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-24 19:22:25 +01:00
|
|
|
cx.spawn(async move |cx| {
|
|
|
|
|
println!("[query] {}", q.key());
|
|
|
|
|
|
|
|
|
|
let result = q.run(&query_context).await;
|
2026-04-20 15:13:26 +01:00
|
|
|
|
2026-04-21 20:30:41 +01:00
|
|
|
entity.raw.update(cx, |state, cx| {
|
2026-04-20 15:13:26 +01:00
|
|
|
state.data = match result {
|
2026-05-24 16:44:10 +01:00
|
|
|
| Ok(data) => {
|
|
|
|
|
println!("[query] OK {}", q.key());
|
|
|
|
|
QueryData::Some(Box::new(data))
|
|
|
|
|
}
|
|
|
|
|
| Err(err) => {
|
|
|
|
|
println!("[query] ERR {:?}: {:?}", q.key(), err);
|
|
|
|
|
QueryData::Err(Box::new(err))
|
|
|
|
|
}
|
2026-04-20 15:13:26 +01:00
|
|
|
};
|
|
|
|
|
cx.notify();
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
anyhow::Ok(())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
async fn execute_query<'a, F>(&mut self, query_fn: &F, cx: &'a mut gpui::AsyncApp) -> Entity<F>
|
|
|
|
|
where
|
|
|
|
|
F: QueryFn<Context = C>,
|
|
|
|
|
{
|
|
|
|
|
let entity = self.entity_for(query_fn, cx).unwrap();
|
|
|
|
|
|
|
|
|
|
entity.update(cx, |query, cx| {
|
|
|
|
|
query.data = QueryData::Loading;
|
|
|
|
|
cx.notify();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let result = query_fn.run(&self.query_context).await;
|
|
|
|
|
|
|
|
|
|
entity
|
|
|
|
|
.raw
|
|
|
|
|
.update(cx, |query, cx| {
|
|
|
|
|
query.data = match result {
|
2026-05-24 16:44:10 +01:00
|
|
|
| Ok(data) => QueryData::Some(Box::new(data)),
|
|
|
|
|
| Err(err) => QueryData::Err(Box::new(err)),
|
2026-04-24 19:22:25 +01:00
|
|
|
};
|
|
|
|
|
cx.notify();
|
|
|
|
|
true
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
entity
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 12:41:33 +01:00
|
|
|
fn invalidate_query<E, F>(&self, entity: &Entity<F>, cx: &mut gpui::Context<E>)
|
2026-04-20 22:54:31 +01:00
|
|
|
where
|
2026-04-22 12:41:33 +01:00
|
|
|
E: 'static,
|
|
|
|
|
F: QueryFn<Context = C>,
|
2026-04-20 22:54:31 +01:00
|
|
|
{
|
2026-05-06 01:42:38 +08:00
|
|
|
entity.update(cx, |query, cx| {
|
|
|
|
|
if !matches!(query.data, QueryData::Loading) {
|
|
|
|
|
query.data = QueryData::Stale;
|
|
|
|
|
cx.notify()
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-04-20 22:54:31 +01:00
|
|
|
}
|
2026-04-20 15:13:26 +01:00
|
|
|
}
|
2026-04-21 11:50:04 +01:00
|
|
|
|
2026-04-24 19:22:25 +01:00
|
|
|
impl QueryAppContext for gpui::App {
|
|
|
|
|
fn ready<T>(value: T) -> Self::Result<T> {
|
|
|
|
|
value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn map_result<T, U>(result: Self::Result<T>, f: impl FnOnce(T) -> U) -> Self::Result<U> {
|
|
|
|
|
f(result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl QueryAppContext for gpui::AsyncApp {
|
|
|
|
|
fn ready<T>(value: T) -> Self::Result<T> {
|
|
|
|
|
Ok(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn map_result<T, U>(result: Self::Result<T>, f: impl FnOnce(T) -> U) -> Self::Result<U> {
|
|
|
|
|
result.map(f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, E> QueryAppContext for gpui::Context<'a, E> {
|
|
|
|
|
fn ready<T>(value: T) -> Self::Result<T> {
|
|
|
|
|
value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn map_result<T, U>(result: Self::Result<T>, f: impl FnOnce(T) -> U) -> Self::Result<U> {
|
|
|
|
|
f(result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 01:42:38 +08:00
|
|
|
impl AsRef<str> for Key {
|
|
|
|
|
fn as_ref(&self) -> &str {
|
|
|
|
|
self.0.as_ref()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&'static str> for Key {
|
|
|
|
|
fn from(key: &'static str) -> Self {
|
|
|
|
|
Key(Cow::Borrowed(key))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<String> for Key {
|
|
|
|
|
fn from(key: String) -> Self {
|
|
|
|
|
Key(Cow::Owned(key))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for Key {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
write!(f, "{}", self.0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 11:50:04 +01:00
|
|
|
impl<C> gpui::Global for Store<C> where C: Context + 'static {}
|