Skip to main content

StreamRenderer

Struct StreamRenderer 

Source
pub struct StreamRenderer { /* private fields */ }
Expand description

Incrementally renders markdown text chunks as they arrive.

StreamRenderer is designed for streaming LLM responses: as the model generates markdown text chunk by chunk, this renderer produces complete, renderable lines as soon as enough input has been buffered to form a complete markdown element (e.g. a paragraph ended by a blank line, a complete table, a closed fenced code block).

§Examples

use smart_markdown::{StreamRenderer, ThemeMode, is_light_terminal};

let width = terminal_size::terminal_size()
    .map(|(w, _)| w.0 as usize)
    .unwrap_or(80);
let theme = if is_light_terminal() { ThemeMode::Light } else { ThemeMode::Dark };
let mut sr = StreamRenderer::new(width, theme)
    .with_ascii_table_borders(true)
    .with_code_theme("base16-ocean.dark");

// Feed chunks as they arrive from the LLM
for line in sr.push("# Hello\n\n") {
    println!("{line}");
}
for line in sr.push("this is **bold** text") {
    println!("{line}");
}

// Flush anything still buffered at the end
for line in sr.flush_remaining() {
    println!("{line}");
}

Implementations§

Source§

impl StreamRenderer

Source

pub fn new(width: usize, theme_mode: ThemeMode) -> Self

Create a new stream renderer.

  • width: terminal width in columns (e.g. from the terminal_size crate).
  • theme_mode: controls syntax highlighting theme for code blocks.
Examples found in repository?
examples/table_streaming.rs (line 7)
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7    let mut sr = StreamRenderer::new(80, ThemeMode::Dark);
8
9    println!("Streaming Table Updates Demo\n--------------------------\n");
10
11    // Simulate LLM streaming table header and separator
12    for line in sr.push("# Real-time Data Analysis\n\n") {
13        println!("{line}");
14    }
15    io::stdout().flush()?;
16    thread::sleep(Duration::from_millis(300));
17
18    for line in sr.push("| Time | Metric | Value | Status |\n") {
19        println!("{line}");
20    }
21    io::stdout().flush()?;
22    thread::sleep(Duration::from_millis(300));
23
24    for line in sr.push("|------|--------|-------|--------|\n") {
25        println!("{line}");
26    }
27    io::stdout().flush()?;
28    thread::sleep(Duration::from_millis(300));
29
30    // Simulate LLM streaming data rows one by one
31    let lines = sr.push("| 10:00 | CPU Usage | 45% | Normal |\n");
32    for line in lines {
33        println!("{line}");
34    }
35    io::stdout().flush()?;
36    thread::sleep(Duration::from_millis(500));
37
38    let lines = sr.push("| 10:05 | CPU Usage | 67% | Warning |\n");
39    for line in lines {
40        println!("{line}");
41    }
42    io::stdout().flush()?;
43    thread::sleep(Duration::from_millis(500));
44
45    let lines = sr.push("| 10:10 | CPU Usage | 82% | Alert |\n");
46    for line in lines {
47        println!("{line}");
48    }
49    io::stdout().flush()?;
50    thread::sleep(Duration::from_millis(500));
51
52    let lines = sr.push("| 10:15 | CPU Usage | 95% | Critical |\n");
53    for line in lines {
54        println!("{line}");
55    }
56    io::stdout().flush()?;
57    thread::sleep(Duration::from_millis(500));
58
59    // Flush any remaining content
60    for line in sr.flush_remaining() {
61        println!("{line}");
62    }
63
64    println!();
65    Ok(())
66}
More examples
Hide additional examples
examples/streaming_demo.rs (line 7)
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7    let mut sr = StreamRenderer::new(80, ThemeMode::Dark);
8
9    println!(
10        "Streaming Markdown Demo with Table Updates\n----------------------------------------\n"
11    );
12
13    // Stream some headings and text
14    for line in sr.push("# Live Data Dashboard\n\n") {
15        println!("{line}");
16    }
17    io::stdout().flush()?;
18    thread::sleep(Duration::from_millis(200));
19
20    for line in sr.push("## System Metrics\n\n") {
21        println!("{line}");
22    }
23    io::stdout().flush()?;
24    thread::sleep(Duration::from_millis(200));
25
26    // Stream table header and separator
27    for line in sr.push("| Component | Status | Value | Trend |\n") {
28        println!("{line}");
29    }
30    io::stdout().flush()?;
31    thread::sleep(Duration::from_millis(200));
32
33    for line in sr.push("|-----------|--------|-------|-------|\n") {
34        println!("{line}");
35    }
36    io::stdout().flush()?;
37    thread::sleep(Duration::from_millis(200));
38
39    // Stream first data row
40    let lines = sr.push("| CPU Usage | Normal | 42%   | →     |\n");
41    for line in lines {
42        println!("{line}");
43    }
44    io::stdout().flush()?;
45    thread::sleep(Duration::from_millis(500));
46
47    // Stream second data row
48    let lines = sr.push("| Memory    | Normal | 67%   | ↗     |\n");
49    for line in lines {
50        println!("{line}");
51    }
52    io::stdout().flush()?;
53    thread::sleep(Duration::from_millis(500));
54
55    // Stream third data row
56    let lines = sr.push("| Disk I/O  | High   | 89%   | ↗↗    |\n");
57    for line in lines {
58        println!("{line}");
59    }
60    io::stdout().flush()?;
61    thread::sleep(Duration::from_millis(500));
62
63    // Add some text after the table
64    for line in sr.push("\n### Alerts\n\n") {
65        println!("{line}");
66    }
67    io::stdout().flush()?;
68    thread::sleep(Duration::from_millis(200));
69
70    // Stream a list
71    for line in sr.push("- High disk I/O detected\n") {
72        println!("{line}");
73    }
74    io::stdout().flush()?;
75    thread::sleep(Duration::from_millis(200));
76
77    for line in sr.push("- Memory usage trending upward\n") {
78        println!("{line}");
79    }
80    io::stdout().flush()?;
81    thread::sleep(Duration::from_millis(200));
82
83    // Flush any remaining content
84    for line in sr.flush_remaining() {
85        println!("{line}");
86    }
87
88    println!();
89    Ok(())
90}
Source

pub fn with_code_theme(self, theme: &str) -> Self

Set a custom syntax highlighting theme by name.

See crate::highlight::list_themes for available theme names.

Source

pub fn with_ascii_table_borders(self, ascii: bool) -> Self

Use ASCII-only table borders (+, -, |) instead of Unicode box-drawing characters (, , , etc.).

Useful for terminals where Unicode box-drawing renders poorly (e.g. light-background themes without proper color inversion).

Source

pub fn push(&mut self, text: &str) -> Vec<String>

Push additional text chunks.

Returns rendered complete lines as they become available. Incomplete markdown (partial fenced blocks, tables, paragraphs) is buffered internally.

Examples found in repository?
examples/table_streaming.rs (line 12)
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7    let mut sr = StreamRenderer::new(80, ThemeMode::Dark);
8
9    println!("Streaming Table Updates Demo\n--------------------------\n");
10
11    // Simulate LLM streaming table header and separator
12    for line in sr.push("# Real-time Data Analysis\n\n") {
13        println!("{line}");
14    }
15    io::stdout().flush()?;
16    thread::sleep(Duration::from_millis(300));
17
18    for line in sr.push("| Time | Metric | Value | Status |\n") {
19        println!("{line}");
20    }
21    io::stdout().flush()?;
22    thread::sleep(Duration::from_millis(300));
23
24    for line in sr.push("|------|--------|-------|--------|\n") {
25        println!("{line}");
26    }
27    io::stdout().flush()?;
28    thread::sleep(Duration::from_millis(300));
29
30    // Simulate LLM streaming data rows one by one
31    let lines = sr.push("| 10:00 | CPU Usage | 45% | Normal |\n");
32    for line in lines {
33        println!("{line}");
34    }
35    io::stdout().flush()?;
36    thread::sleep(Duration::from_millis(500));
37
38    let lines = sr.push("| 10:05 | CPU Usage | 67% | Warning |\n");
39    for line in lines {
40        println!("{line}");
41    }
42    io::stdout().flush()?;
43    thread::sleep(Duration::from_millis(500));
44
45    let lines = sr.push("| 10:10 | CPU Usage | 82% | Alert |\n");
46    for line in lines {
47        println!("{line}");
48    }
49    io::stdout().flush()?;
50    thread::sleep(Duration::from_millis(500));
51
52    let lines = sr.push("| 10:15 | CPU Usage | 95% | Critical |\n");
53    for line in lines {
54        println!("{line}");
55    }
56    io::stdout().flush()?;
57    thread::sleep(Duration::from_millis(500));
58
59    // Flush any remaining content
60    for line in sr.flush_remaining() {
61        println!("{line}");
62    }
63
64    println!();
65    Ok(())
66}
More examples
Hide additional examples
examples/streaming_demo.rs (line 14)
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7    let mut sr = StreamRenderer::new(80, ThemeMode::Dark);
8
9    println!(
10        "Streaming Markdown Demo with Table Updates\n----------------------------------------\n"
11    );
12
13    // Stream some headings and text
14    for line in sr.push("# Live Data Dashboard\n\n") {
15        println!("{line}");
16    }
17    io::stdout().flush()?;
18    thread::sleep(Duration::from_millis(200));
19
20    for line in sr.push("## System Metrics\n\n") {
21        println!("{line}");
22    }
23    io::stdout().flush()?;
24    thread::sleep(Duration::from_millis(200));
25
26    // Stream table header and separator
27    for line in sr.push("| Component | Status | Value | Trend |\n") {
28        println!("{line}");
29    }
30    io::stdout().flush()?;
31    thread::sleep(Duration::from_millis(200));
32
33    for line in sr.push("|-----------|--------|-------|-------|\n") {
34        println!("{line}");
35    }
36    io::stdout().flush()?;
37    thread::sleep(Duration::from_millis(200));
38
39    // Stream first data row
40    let lines = sr.push("| CPU Usage | Normal | 42%   | →     |\n");
41    for line in lines {
42        println!("{line}");
43    }
44    io::stdout().flush()?;
45    thread::sleep(Duration::from_millis(500));
46
47    // Stream second data row
48    let lines = sr.push("| Memory    | Normal | 67%   | ↗     |\n");
49    for line in lines {
50        println!("{line}");
51    }
52    io::stdout().flush()?;
53    thread::sleep(Duration::from_millis(500));
54
55    // Stream third data row
56    let lines = sr.push("| Disk I/O  | High   | 89%   | ↗↗    |\n");
57    for line in lines {
58        println!("{line}");
59    }
60    io::stdout().flush()?;
61    thread::sleep(Duration::from_millis(500));
62
63    // Add some text after the table
64    for line in sr.push("\n### Alerts\n\n") {
65        println!("{line}");
66    }
67    io::stdout().flush()?;
68    thread::sleep(Duration::from_millis(200));
69
70    // Stream a list
71    for line in sr.push("- High disk I/O detected\n") {
72        println!("{line}");
73    }
74    io::stdout().flush()?;
75    thread::sleep(Duration::from_millis(200));
76
77    for line in sr.push("- Memory usage trending upward\n") {
78        println!("{line}");
79    }
80    io::stdout().flush()?;
81    thread::sleep(Duration::from_millis(200));
82
83    // Flush any remaining content
84    for line in sr.flush_remaining() {
85        println!("{line}");
86    }
87
88    println!();
89    Ok(())
90}
Source

pub fn flush_remaining(&mut self) -> Vec<String>

Flush any remaining buffered content and return the final lines.

Call this once at the end of the stream to emit any markdown that hasn’t been completed by a blank line or structural close.

Examples found in repository?
examples/table_streaming.rs (line 60)
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7    let mut sr = StreamRenderer::new(80, ThemeMode::Dark);
8
9    println!("Streaming Table Updates Demo\n--------------------------\n");
10
11    // Simulate LLM streaming table header and separator
12    for line in sr.push("# Real-time Data Analysis\n\n") {
13        println!("{line}");
14    }
15    io::stdout().flush()?;
16    thread::sleep(Duration::from_millis(300));
17
18    for line in sr.push("| Time | Metric | Value | Status |\n") {
19        println!("{line}");
20    }
21    io::stdout().flush()?;
22    thread::sleep(Duration::from_millis(300));
23
24    for line in sr.push("|------|--------|-------|--------|\n") {
25        println!("{line}");
26    }
27    io::stdout().flush()?;
28    thread::sleep(Duration::from_millis(300));
29
30    // Simulate LLM streaming data rows one by one
31    let lines = sr.push("| 10:00 | CPU Usage | 45% | Normal |\n");
32    for line in lines {
33        println!("{line}");
34    }
35    io::stdout().flush()?;
36    thread::sleep(Duration::from_millis(500));
37
38    let lines = sr.push("| 10:05 | CPU Usage | 67% | Warning |\n");
39    for line in lines {
40        println!("{line}");
41    }
42    io::stdout().flush()?;
43    thread::sleep(Duration::from_millis(500));
44
45    let lines = sr.push("| 10:10 | CPU Usage | 82% | Alert |\n");
46    for line in lines {
47        println!("{line}");
48    }
49    io::stdout().flush()?;
50    thread::sleep(Duration::from_millis(500));
51
52    let lines = sr.push("| 10:15 | CPU Usage | 95% | Critical |\n");
53    for line in lines {
54        println!("{line}");
55    }
56    io::stdout().flush()?;
57    thread::sleep(Duration::from_millis(500));
58
59    // Flush any remaining content
60    for line in sr.flush_remaining() {
61        println!("{line}");
62    }
63
64    println!();
65    Ok(())
66}
More examples
Hide additional examples
examples/streaming_demo.rs (line 84)
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7    let mut sr = StreamRenderer::new(80, ThemeMode::Dark);
8
9    println!(
10        "Streaming Markdown Demo with Table Updates\n----------------------------------------\n"
11    );
12
13    // Stream some headings and text
14    for line in sr.push("# Live Data Dashboard\n\n") {
15        println!("{line}");
16    }
17    io::stdout().flush()?;
18    thread::sleep(Duration::from_millis(200));
19
20    for line in sr.push("## System Metrics\n\n") {
21        println!("{line}");
22    }
23    io::stdout().flush()?;
24    thread::sleep(Duration::from_millis(200));
25
26    // Stream table header and separator
27    for line in sr.push("| Component | Status | Value | Trend |\n") {
28        println!("{line}");
29    }
30    io::stdout().flush()?;
31    thread::sleep(Duration::from_millis(200));
32
33    for line in sr.push("|-----------|--------|-------|-------|\n") {
34        println!("{line}");
35    }
36    io::stdout().flush()?;
37    thread::sleep(Duration::from_millis(200));
38
39    // Stream first data row
40    let lines = sr.push("| CPU Usage | Normal | 42%   | →     |\n");
41    for line in lines {
42        println!("{line}");
43    }
44    io::stdout().flush()?;
45    thread::sleep(Duration::from_millis(500));
46
47    // Stream second data row
48    let lines = sr.push("| Memory    | Normal | 67%   | ↗     |\n");
49    for line in lines {
50        println!("{line}");
51    }
52    io::stdout().flush()?;
53    thread::sleep(Duration::from_millis(500));
54
55    // Stream third data row
56    let lines = sr.push("| Disk I/O  | High   | 89%   | ↗↗    |\n");
57    for line in lines {
58        println!("{line}");
59    }
60    io::stdout().flush()?;
61    thread::sleep(Duration::from_millis(500));
62
63    // Add some text after the table
64    for line in sr.push("\n### Alerts\n\n") {
65        println!("{line}");
66    }
67    io::stdout().flush()?;
68    thread::sleep(Duration::from_millis(200));
69
70    // Stream a list
71    for line in sr.push("- High disk I/O detected\n") {
72        println!("{line}");
73    }
74    io::stdout().flush()?;
75    thread::sleep(Duration::from_millis(200));
76
77    for line in sr.push("- Memory usage trending upward\n") {
78        println!("{line}");
79    }
80    io::stdout().flush()?;
81    thread::sleep(Duration::from_millis(200));
82
83    // Flush any remaining content
84    for line in sr.flush_remaining() {
85        println!("{line}");
86    }
87
88    println!();
89    Ok(())
90}

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.