Skip to main content

flywheel/terminal/
output.rs

1//! `OutputBuffer`: Single-syscall output buffer for ANSI sequences.
2
3use crate::buffer::Rgb;
4use std::io::Write;
5
6/// Pre-allocated buffer for building ANSI escape sequences.
7///
8/// All output is accumulated here, then flushed in a single `write()` syscall
9/// to prevent terminal flickering.
10pub struct OutputBuffer {
11    data: Vec<u8>,
12}
13
14impl OutputBuffer {
15    /// Create a new output buffer with the given capacity.
16    pub fn with_capacity(capacity: usize) -> Self {
17        Self {
18            data: Vec::with_capacity(capacity),
19        }
20    }
21
22    /// Create a buffer sized for a typical terminal (4KB).
23    pub fn new() -> Self {
24        Self::with_capacity(4096)
25    }
26
27    /// Clear the buffer for reuse.
28    #[inline]
29    pub fn clear(&mut self) {
30        self.data.clear();
31    }
32
33    /// Get the buffer contents.
34    #[inline]
35    pub fn as_bytes(&self) -> &[u8] {
36        &self.data
37    }
38
39    /// Get the buffer length.
40    #[inline]
41    pub const fn len(&self) -> usize {
42        self.data.len()
43    }
44
45    /// Check if buffer is empty.
46    #[inline]
47    pub const fn is_empty(&self) -> bool {
48        self.data.is_empty()
49    }
50
51    /// Write raw bytes.
52    #[inline]
53    pub fn write_raw(&mut self, bytes: &[u8]) {
54        self.data.extend_from_slice(bytes);
55    }
56
57    /// Write a string.
58    #[inline]
59    pub fn write_str(&mut self, s: &str) {
60        self.data.extend_from_slice(s.as_bytes());
61    }
62
63    /// Move cursor to (x, y) position (1-indexed for ANSI).
64    #[inline]
65    pub fn cursor_move(&mut self, x: u16, y: u16) {
66        // CSI row ; col H
67        write!(self.data, "\x1b[{};{}H", y + 1, x + 1).unwrap();
68    }
69
70    /// Hide cursor.
71    #[inline]
72    pub fn cursor_hide(&mut self) {
73        self.data.extend_from_slice(b"\x1b[?25l");
74    }
75
76    /// Show cursor.
77    #[inline]
78    pub fn cursor_show(&mut self) {
79        self.data.extend_from_slice(b"\x1b[?25h");
80    }
81
82    /// Set foreground color (true color).
83    #[inline]
84    pub fn set_fg(&mut self, color: Rgb) {
85        write!(self.data, "\x1b[38;2;{};{};{}m", color.r, color.g, color.b).unwrap();
86    }
87
88    /// Set background color (true color).
89    #[inline]
90    pub fn set_bg(&mut self, color: Rgb) {
91        write!(self.data, "\x1b[48;2;{};{};{}m", color.r, color.g, color.b).unwrap();
92    }
93
94    /// Reset all attributes.
95    #[inline]
96    pub fn reset_attrs(&mut self) {
97        self.data.extend_from_slice(b"\x1b[0m");
98    }
99
100    /// Clear the entire screen.
101    #[inline]
102    pub fn clear_screen(&mut self) {
103        self.data.extend_from_slice(b"\x1b[2J");
104    }
105
106    /// Flush to a writer in a single syscall.
107    ///
108    /// # Errors
109    ///
110    /// Returns an error if the underlying writer fails.
111    pub fn flush_to<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
112        writer.write_all(&self.data)?;
113        writer.flush()
114    }
115}
116
117impl Default for OutputBuffer {
118    fn default() -> Self {
119        Self::new()
120    }
121}