Kirill BulatovRework task modal (#10341)d1ad9672 years agoHistory
134 lines · 4.8 KB · Rust
RawDownload
//! A source of tasks, based on a static configuration, deserialized from the tasks config file, and related infrastructure for tracking changes to the file. use futures::StreamExt;use gpui::{AppContext, Context, Model, ModelContext, Subscription};use serde::Deserialize;use util::ResultExt; use crate::{TaskSource, TaskTemplates};use futures::channel::mpsc::UnboundedReceiver; /// The source of tasks defined in a tasks config file.pub struct StaticSource {    tasks: TaskTemplates,    _templates: Model<TrackedFile<TaskTemplates>>,    _subscription: Subscription,} /// A Wrapper around deserializable T that keeps track of its contents/// via a provided channel. Once T value changes, the observers of [`TrackedFile`] are/// notified.pub struct TrackedFile<T> {    parsed_contents: T,} impl<T: PartialEq + 'static> TrackedFile<T> {    /// Initializes new [`TrackedFile`] with a type that's deserializable.    pub fn new(mut tracker: UnboundedReceiver<String>, cx: &mut AppContext) -> Model<Self>    where        T: for<'a> Deserialize<'a> + Default,    {        cx.new_model(move |cx| {            cx.spawn(|tracked_file, mut cx| async move {                while let Some(new_contents) = tracker.next().await {                    if !new_contents.trim().is_empty() {                        // String -> T (ZedTaskFormat)                        // String -> U (VsCodeFormat) -> Into::into T                        let Some(new_contents) =                            serde_json_lenient::from_str(&new_contents).log_err()                        else {                            continue;                        };                        tracked_file.update(&mut cx, |tracked_file: &mut TrackedFile<T>, cx| {                            if tracked_file.parsed_contents != new_contents {                                tracked_file.parsed_contents = new_contents;                                cx.notify();                            };                        })?;                    }                }                anyhow::Ok(())            })            .detach_and_log_err(cx);            Self {                parsed_contents: Default::default(),            }        })    }     /// Initializes new [`TrackedFile`] with a type that's convertible from another deserializable type.    pub fn new_convertible<U: for<'a> Deserialize<'a> + TryInto<T, Error = anyhow::Error>>(        mut tracker: UnboundedReceiver<String>,        cx: &mut AppContext,    ) -> Model<Self>    where        T: Default,    {        cx.new_model(move |cx| {            cx.spawn(|tracked_file, mut cx| async move {                while let Some(new_contents) = tracker.next().await {                    if !new_contents.trim().is_empty() {                        let Some(new_contents) =                            serde_json_lenient::from_str::<U>(&new_contents).log_err()                        else {                            continue;                        };                        let Some(new_contents) = new_contents.try_into().log_err() else {                            continue;                        };                        tracked_file.update(&mut cx, |tracked_file: &mut TrackedFile<T>, cx| {                            if tracked_file.parsed_contents != new_contents {                                tracked_file.parsed_contents = new_contents;                                cx.notify();                            };                        })?;                    }                }                anyhow::Ok(())            })            .detach_and_log_err(cx);            Self {                parsed_contents: Default::default(),            }        })    }     fn get(&self) -> &T {        &self.parsed_contents    }} impl StaticSource {    /// Initializes the static source, reacting on tasks config changes.    pub fn new(        templates: Model<TrackedFile<TaskTemplates>>,        cx: &mut AppContext,    ) -> Model<Box<dyn TaskSource>> {        cx.new_model(|cx| {            let _subscription = cx.observe(                &templates,                move |source: &mut Box<(dyn TaskSource + 'static)>, new_templates, cx| {                    if let Some(static_source) = source.as_any().downcast_mut::<Self>() {                        static_source.tasks = new_templates.read(cx).get().clone();                        cx.notify();                    }                },            );            Box::new(Self {                tasks: TaskTemplates::default(),                _templates: templates,                _subscription,            })        })    }} impl TaskSource for StaticSource {    fn tasks_to_schedule(&mut self, _: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates {        self.tasks.clone()    }     fn as_any(&mut self) -> &mut dyn std::any::Any {        self    }}