1use std::{io, path::Path, time::Duration};
12
13use omp_tui::{AppOptions, Key, Ui, UiContext, dom};
14
15fn build(source: &str, width: u16, ctx: &UiContext) -> Ui {
18 match Ui::from_markup(source, width, ctx.clone()) {
19 Ok(ui) => ui,
20 Err(error) => {
21 let message = error.to_string();
22 Ui::from_root(
23 dom! {
24 <box border=round bc=err title="parse error" pad="0 1">
25 <text fg=err>{message}</text>
26 </box>
27 },
28 width,
29 ctx.clone(),
30 )
31 },
32 }
33}
34
35fn modified(path: &Path) -> Option<std::time::SystemTime> {
36 std::fs::metadata(path)
37 .and_then(|meta| meta.modified())
38 .ok()
39}
40
41#[tokio::main]
42async fn main() -> io::Result<()> {
43 let path = std::env::args()
44 .nth(1)
45 .unwrap_or_else(|| "example.tml".into());
46 let source = std::fs::read_to_string(&path)?;
47
48 let mut ctx = None;
49 let mut app = AppOptions::new()
50 .quit([Key::Ctrl('c'), Key::Char('q'), Key::Esc])
51 .start(|env| {
52 let ui = build(&source, env.viewport.width, &env.ctx);
53 ctx = Some(env.ctx);
54 ui
55 })
56 .await?;
57 let ctx = ctx.expect("start ran the builder");
58
59 let handle = app.handle();
60 tokio::spawn(async move {
61 let mut seen = modified(path.as_ref());
62 loop {
63 tokio::time::sleep(Duration::from_millis(150)).await;
64 let stamp = modified(path.as_ref());
65 if stamp == seen {
66 continue;
67 }
68 seen = stamp;
69 let Ok(source) = std::fs::read_to_string(&path) else {
70 continue;
71 };
72 let ctx = ctx.clone();
73 handle.update(move |ui| *ui = build(&source, ui.frame().size().width, &ctx));
74 }
75 });
76
77 while app.next().await?.is_some() {}
78 Ok(())
79}