Skip to main content

tml/
tml.rs

1//! Renders a runtime-markup document and live-reloads it on save.
2//!
3//! ```sh
4//! cargo run -p omp-tui --example tml -- example.tml
5//! ```
6//!
7//! Edit the file in another window; the screen repaints on every save. A
8//! parse error shows as an error card until the next good save. Quit with
9//! `q`, Escape, or Ctrl-C.
10
11use std::{io, path::Path, time::Duration};
12
13use omp_tui::{AppOptions, Key, Ui, UiContext, dom};
14
15/// Parses `source`, degrading a [`omp_tui::ParseError`] to an error card so a
16/// mid-edit typo never kills the session.
17fn 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}