Skip to main content

chunk_flush/
lib.rs

1//! # chunk-flush
2//!
3//! Flush-on-newline buffer for streaming LLM output. Holds chars until
4//! either a newline arrives or `max_pending_chars` is exceeded, then
5//! emits the buffered chunk.
6//!
7//! The point: keep the UI/log from churning on every single token
8//! delta, without making it laggy on long lines.
9//!
10//! ## Example
11//!
12//! ```
13//! use chunk_flush::Flusher;
14//! let mut f = Flusher::new(500);
15//! assert_eq!(f.push("hello"), None);     // no \n yet, under cap
16//! assert_eq!(f.push(" world\n").as_deref(), Some("hello world\n"));
17//! ```
18
19#![deny(missing_docs)]
20
21/// Streaming buffer with flush-on-newline + size cap.
22#[derive(Debug, Clone)]
23pub struct Flusher {
24    buf: String,
25    max_pending_chars: usize,
26}
27
28impl Flusher {
29    /// Build a flusher with a max-pending cap. Beyond this many chars
30    /// the buffer auto-flushes even without a newline.
31    pub fn new(max_pending_chars: usize) -> Self {
32        Self {
33            buf: String::new(),
34            max_pending_chars,
35        }
36    }
37
38    /// Push next chunk. Returns the new emitted chunk when one is
39    /// ready, else `None`.
40    pub fn push(&mut self, chunk: &str) -> Option<String> {
41        self.buf.push_str(chunk);
42        // If the buffer contains a newline, flush everything up to
43        // (and including) the last newline.
44        if let Some(last_nl) = self.buf.rfind('\n') {
45            let split = last_nl + 1;
46            let out: String = self.buf.drain(..split).collect();
47            return Some(out);
48        }
49        // No newline yet — check the size cap.
50        if self.buf.chars().count() > self.max_pending_chars {
51            return Some(self.flush());
52        }
53        None
54    }
55
56    /// Force-flush whatever is buffered.
57    pub fn flush(&mut self) -> String {
58        std::mem::take(&mut self.buf)
59    }
60
61    /// True when nothing is buffered.
62    pub fn is_empty(&self) -> bool {
63        self.buf.is_empty()
64    }
65}