diff --git a/Cargo.toml b/Cargo.toml index 61028c7..9ce4bb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" anyhow = "1" gpui = { version = "*" } paste = "1.0" +reqwest = "0.13.2" diff --git a/src/api.rs b/src/api.rs index cb55580..4450b12 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1 +1,9 @@ -pub(crate) mod repo; \ No newline at end of file +use crate::query; + +pub(crate) mod repo; + +pub struct QueryContext { + pub(crate) http: reqwest::Client, +} + +impl query::Context for QueryContext {} \ No newline at end of file diff --git a/src/api/repo.rs b/src/api/repo.rs index 88cd45d..09c8993 100644 --- a/src/api/repo.rs +++ b/src/api/repo.rs @@ -1,9 +1,10 @@ +use crate::api::QueryContext; use crate::query; #[derive(Clone)] pub struct List; -impl query::QueryFn for List { +impl query::QueryFn for List { type Data = (); type Error = (); @@ -11,7 +12,6 @@ impl query::QueryFn for List { "repo.list" } - async fn run(&self) -> Result { - todo!() + async fn run(&self, c: &QueryContext) -> Result { } } diff --git a/src/app.rs b/src/app.rs index d4af42b..a15a5a5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,6 @@ use gpui::{div, prelude::*}; -use crate::app; +use crate::{api, app}; use crate::query; use crate::dashboard; use crate::theme; @@ -9,7 +9,7 @@ use crate::titlebar; pub struct Global { pub safe_area: gpui::Bounds, pub current_theme: theme::Theme, - pub query_store: query::Store + pub query_store: query::Store } pub struct Chrome {} @@ -53,6 +53,6 @@ pub fn current_theme<'a, E>(cx: &'a gpui::Context) -> &'a theme::Theme { &cx.global::().current_theme } -pub fn query_store<'a, E>(cx: &'a gpui::Context) -> &'a query::Store { +pub fn query_store<'a, E>(cx: &'a gpui::Context) -> &'a query::Store { &cx.global::().query_store } diff --git a/src/main.rs b/src/main.rs index a393b73..1b3e834 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,9 @@ fn setup_application(cx: &mut gpui::App) { let global = app::Global { safe_area: bounds(point(px(0.), px(0.)), size(px(72.), px(12.))), current_theme: cx.window_appearance().into(), - query_store: query::Store::new(), + query_store: query::Store::new(api::QueryContext { + http: reqwest::Client::new(), + }), }; let top_left = global.safe_area.origin; diff --git a/src/query.rs b/src/query.rs index acd49f1..c3230c0 100644 --- a/src/query.rs +++ b/src/query.rs @@ -2,13 +2,16 @@ use crate::app; use gpui::{AppContext, BorrowAppContext}; use std::any::Any; -pub trait QueryFn: Clone + 'static { +pub trait QueryFn: Clone + 'static +where + C: Context + 'static, +{ type Data: 'static; type Error: 'static; fn key(&self) -> &'static str; - async fn run(&self) -> Result; + async fn run(&self, c: &C) -> Result; } pub enum QueryStatus<'a, Data, Error> { @@ -17,25 +20,29 @@ pub enum QueryStatus<'a, Data, Error> { Err(&'a Error), } -pub fn use_query(query_fn: F, cx: &mut gpui::Context) +pub fn use_query(query_fn: F, cx: &mut gpui::Context) where - F: QueryFn, + C: Context + 'static, + F: QueryFn, T: 'static, + Store: gpui::Global, { - let ent = cx.update_global::(|global, cx| { - let entity = global.query_store.ensure_query(&query_fn, cx); - - if entity.read_with(cx, |state, _| matches!(state.data, QueryData::Pending)) { - global.query_store.execute_query(query_fn, entity.clone(), cx).detach(); - } - - entity - }); + let ent = cx.update_global::, _>(|store, cx| store.ensure_query_data(&query_fn, cx)); cx.observe(&ent, |_, _, cx| { cx.notify(); }) .detach(); + + let query_context = cx.global::>().query_context.clone(); + + cx.observe(&query_context, move |_, _, cx| { + cx.update_global::, _>(|store, cx| { + store.invalidate_query(&query_fn, cx); + store.ensure_query_data(&query_fn, cx); + }) + }) + .detach(); } // ================= Store ================== @@ -47,26 +54,37 @@ pub(crate) struct QueryState { pub(crate) enum QueryData { Pending, Loading, + Stale, Some(Box), Err(Box), } pub(crate) type Entity = gpui::Entity; -pub struct Store { +pub(crate) trait Context {} + +pub struct Store +where + C: Context, +{ query_data: std::collections::HashMap, + query_context: gpui::Entity, } -impl Store { - pub fn new() -> Self { +impl Store +where + C: Context + 'static, +{ + pub fn new(ctx: C, cx: &mut gpui::Context) -> Self { Self { query_data: std::collections::HashMap::new(), + query_context: cx.new(|_| ctx), } } - fn ensure_query(&mut self, query: &Q, cx: &mut gpui::Context) -> Entity + fn entity_for(&mut self, query: &Q, cx: &mut gpui::Context) -> Entity where - Q: QueryFn, + Q: QueryFn, T: 'static, { self.query_data @@ -79,23 +97,43 @@ impl Store { .clone() } + fn ensure_query_data(&mut self, query: &Q, cx: &mut gpui::Context) -> Entity + where + T: 'static, + Q: QueryFn, + { + 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 + } + fn execute_query( &mut self, - query: Q, - entity: Entity, + query: &Q, cx: &mut gpui::Context, ) -> gpui::Task> where - Q: QueryFn, + Q: QueryFn, T: 'static, { + let entity = self.entity_for(query, cx); + entity.update(cx, |state, cx| { state.data = QueryData::Loading; cx.notify(); }); cx.spawn(async move |_, cx| { - let result = query.run().await; + let query_context = self.query_context.read(cx); + let result = query.run(query_context).await; entity.update(cx, |state, cx| { state.data = match result { @@ -109,15 +147,36 @@ impl Store { }) } - pub(crate) fn read_query<'a, Q, E>(&self, query_fn: Q, cx: &'a gpui::Context) -> QueryStatus<'a, Q::Data, Q::Error> where Q: QueryFn { + fn invalidate_query(&mut self, query: &Q, cx: &mut gpui::Context) + where + Q: QueryFn, + { + 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, + ) -> QueryStatus<'a, Q::Data, Q::Error> + where + Q: QueryFn, + { let Some(ent) = self.query_data.get(query_fn.key()) else { return QueryStatus::Loading; }; let QueryState { data } = ent.read(cx); match data { - QueryData::Loading | QueryData::Pending => QueryStatus::Loading, + QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading, QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::().unwrap()), - QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::().unwrap()) + QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::().unwrap()), } } }