snowcap 0.1.3

A simple markup language for iced
Documentation
use std::fs;

use iced::{
    futures::{self, channel::mpsc::channel, SinkExt},
    Element, Task, Theme,
};
use notify::{event::ModifyKind, RecommendedWatcher, Watcher};
use snowcap::{Snowcap, SnowcapParser};
use tracing::{debug, error, info};
use tracing_subscriber;

pub fn main() -> iced::Result {
    tracing_subscriber::fmt::init();

    let args: Vec<String> = std::env::args().collect();

    let filename = args[1].clone();

    let (mut tx, rx) = channel(1);
    let mut watcher = RecommendedWatcher::new(
        move |res| {
            futures::executor::block_on(async {
                tx.send(res).await.unwrap();
            })
        },
        notify::Config::default(),
    )
    .unwrap();

    watcher
        .watch(filename.as_ref(), notify::RecursiveMode::Recursive)
        .unwrap();

    iced::application("Snowcap", SnowcapViewer::update, SnowcapViewer::view)
        .theme(SnowcapViewer::theme)
        .run_with(move || {
            let viewer = SnowcapViewer::new(filename.clone());
            (
                // Set the initial state
                viewer,
                // Initial task to read events from the watcher channel and wrap them in a Message
                Task::run(rx, |event| {
                    snowcap::Message::App(Message::Watcher(event.unwrap()))
                }),
            )
        })
}

struct SnowcapViewer {
    filename: String,
    parse_error: Option<snowcap::Error>,
    root: Option<Snowcap<Message>>,
}

impl SnowcapViewer {
    pub fn new(filename: String) -> Self {
        let mut viewer = Self {
            filename,
            root: None,
            parse_error: None,
        };
        viewer.load().ok();
        viewer
    }

    pub fn load(&mut self) -> Result<(), snowcap::Error> {
        let data = fs::read_to_string(&self.filename).expect("cannot read file");

        match SnowcapParser::parse_file(&data) {
            Ok(root) => {
                self.root = Some(root);
                debug!("{:#?}", self.root);
                self.parse_error = None;
            }
            Err(e) => {
                self.root = None;
                self.parse_error = Some(e);
            }
        }

        Ok(())
    }
}

#[derive(Debug, Clone)]
enum Message {
    /// A variant for handling file system watcher events.
    ///
    /// This variant contains a [`notify::Event`] that encapsulates file
    /// system changes, useful for tracking changes in files or directories.
    Watcher(notify::Event),
}

impl SnowcapViewer {
    fn update(&mut self, message: snowcap::Message<Message>) {
        match message {
            snowcap::Message::App(app) => match app {
                Message::Watcher(event) => {
                    debug!("Watcher {event:?}");
                    match event.kind {
                        notify::EventKind::Modify(ModifyKind::Data(_)) => {
                            info!("Snowcap File Modified. Reloading");
                            self.load().ok();
                        }
                        _ => {}
                    }
                }
            },
            snowcap::Message::Markdown(url) => {
                info!("Markdown URL click {url}")
            }
            snowcap::Message::Button => {
                info!("Button clicked")
            }
            snowcap::Message::Toggler(toggled) => {
                info!("Toggler {toggled}")
            }
            snowcap::Message::PickListSelected(selected) => {
                info!("Picklist selected {selected}")
            }
        }
    }

    fn view(&self) -> Element<snowcap::Message<Message>> {
        if let Some(root) = &self.root {
            match root.root().try_into() {
                Ok(content) => content,
                Err(e) => {
                    error!("{e:?}");
                    iced::widget::text(format!("{e:?}")).into()
                }
            }
        } else if let Some(err) = &self.parse_error {
            iced::widget::text(format!("{err:#?}")).into()
        } else {
            iced::widget::text("No snowcap file loaded").into()
        }
    }

    fn theme(&self) -> Theme {
        //Theme::TokyoNight
        Theme::CatppuccinFrappe
    }
}