Skip to main content

markdown_demo/
markdown_demo.rs

1use std::io::{self, Write};
2use std::thread;
3use std::time::{Duration, Instant};
4
5use eye_declare::{InlineRenderer, Markdown, Spinner, Text, VStack};
6use ratatui_core::style::{Color, Modifier, Style};
7
8fn main() -> io::Result<()> {
9    let (width, _) = crossterm::terminal::size()?;
10    let mut r = InlineRenderer::new(width);
11    let mut stdout = io::stdout();
12
13    // User prompt
14    let _prompt = r.push(Text::styled(
15        "› Explain how async/await works in Rust with an example",
16        Style::default()
17            .fg(Color::White)
18            .add_modifier(Modifier::BOLD),
19    ));
20    flush(&mut r, &mut stdout)?;
21
22    // Spacer
23    let _sp = r.push(Text::unstyled(""));
24
25    // Response container
26    let response = r.push(VStack);
27
28    // Thinking spinner
29    let think = r.append_child(response, Spinner::new("Thinking..."));
30    animate_spinner(&mut r, &mut stdout, think, Duration::from_millis(1200))?;
31    r.swap_component(think, Spinner::new("Thinking...").done("Thought for 1.2s"));
32    flush(&mut r, &mut stdout)?;
33    r.freeze(think);
34
35    // Stream the markdown response by rebuilding with progressively more content
36    let md_id = r.append_child(response, Markdown::new(""));
37
38    let response_text = r#"## Async/Await in Rust
39
40Rust's async/await is built on the **Future** trait. When you write an `async fn`, the compiler transforms it into a state machine that implements `Future`.
41
42### Key Concepts
43
44- **Futures are lazy** — they don't run until polled by an *executor*
45- The `await` keyword yields control back to the executor
46- An executor like `tokio` manages scheduling futures onto threads
47
48### Example
49
50```rust
51async fn fetch_data(url: &str) -> Result<String, Error> {
52    let response = reqwest::get(url).await?;
53    let body = response.text().await?;
54    Ok(body)
55}
56
57#[tokio::main]
58async fn main() {
59    let data = fetch_data("https://example.com").await;
60    println!("Got: {:?}", data);
61}
62```
63
64The `.await` points are where the runtime can **suspend** this task and run others. This is *cooperative* multitasking — tasks must explicitly yield via `await`."#;
65
66    // Stream token by token
67    let tokens: Vec<&str> = response_text
68        .split_inclusive(|c: char| c.is_whitespace() || c == '\n')
69        .collect();
70    let mut accumulated = String::new();
71    for token in &tokens {
72        accumulated.push_str(token);
73        r.swap_component(md_id, Markdown::new(accumulated.clone()));
74        flush(&mut r, &mut stdout)?;
75        thread::sleep(Duration::from_millis(20));
76    }
77
78    println!();
79    Ok(())
80}
81
82fn flush(r: &mut InlineRenderer, stdout: &mut impl Write) -> io::Result<()> {
83    let output = r.render();
84    if !output.is_empty() {
85        stdout.write_all(&output)?;
86        stdout.flush()?;
87    }
88    Ok(())
89}
90
91fn animate_spinner(
92    r: &mut InlineRenderer,
93    stdout: &mut impl Write,
94    id: eye_declare::NodeId,
95    duration: Duration,
96) -> io::Result<()> {
97    let start = Instant::now();
98    while start.elapsed() < duration {
99        r.state_mut::<Spinner>(id).tick();
100        flush(r, stdout)?;
101        thread::sleep(Duration::from_millis(80));
102    }
103    Ok(())
104}