Skip to main content

rpi_tui/
stdin_buffer.rs

1//! Input buffering for batch splitting.
2//!
3//! Buffers stdin input to handle bracketed paste mode and other
4//! batch input scenarios.
5
6use std::collections::VecDeque;
7use std::sync::{Arc, Mutex};
8use std::time::{Duration, Instant};
9
10/// Default batch timeout in milliseconds.
11const DEFAULT_BATCH_TIMEOUT_MS: u64 = 16; // ~60fps
12
13/// Maximum buffer size.
14const MAX_BUFFER_SIZE: usize = 64 * 1024; // 64KB
15
16/// Options for stdin buffer.
17#[derive(Debug, Clone)]
18pub struct StdinBufferOptions {
19    /// Maximum time to wait before flushing a batch.
20    pub batch_timeout_ms: u64,
21    /// Maximum buffer size before forced flush.
22    pub max_buffer_size: usize,
23    /// Enable bracketed paste mode handling.
24    pub handle_bracketed_paste: bool,
25}
26
27impl Default for StdinBufferOptions {
28    fn default() -> Self {
29        Self {
30            batch_timeout_ms: DEFAULT_BATCH_TIMEOUT_MS,
31            max_buffer_size: MAX_BUFFER_SIZE,
32            handle_bracketed_paste: true,
33        }
34    }
35}
36
37/// Event types emitted by the stdin buffer.
38#[derive(Debug, Clone)]
39pub enum StdinBufferEvent {
40    /// Normal input data.
41    Data(String),
42    /// Start of bracketed paste.
43    PasteStart,
44    /// End of bracketed paste with the pasted content.
45    PasteEnd(String),
46    /// Batch of input data.
47    Batch(String),
48}
49
50/// Stdin buffer for handling batch input and bracketed paste.
51pub struct StdinBuffer {
52    options: StdinBufferOptions,
53    buffer: Mutex<String>,
54    events: Mutex<VecDeque<StdinBufferEvent>>,
55    in_paste: Mutex<bool>,
56    paste_buffer: Mutex<String>,
57    last_input: Mutex<Option<Instant>>,
58    callbacks: Mutex<Vec<Arc<dyn Fn(&StdinBufferEvent) + Send + Sync>>>,
59}
60
61impl StdinBuffer {
62    /// Create a new stdin buffer with default options.
63    pub fn new() -> Self {
64        Self::with_options(StdinBufferOptions::default())
65    }
66
67    /// Create a new stdin buffer with options.
68    pub fn with_options(options: StdinBufferOptions) -> Self {
69        Self {
70            options,
71            buffer: Mutex::new(String::new()),
72            events: Mutex::new(VecDeque::new()),
73            in_paste: Mutex::new(false),
74            paste_buffer: Mutex::new(String::new()),
75            last_input: Mutex::new(None),
76            callbacks: Mutex::new(Vec::new()),
77        }
78    }
79
80    /// Add a callback for events.
81    pub fn on_event(&self, callback: Arc<dyn Fn(&StdinBufferEvent) + Send + Sync>) {
82        if let Ok(mut callbacks) = self.callbacks.lock() {
83            callbacks.push(callback);
84        }
85    }
86
87    /// Process input data.
88    pub fn process(&self, data: &str) {
89        if data.is_empty() {
90            return;
91        }
92
93        // Update last input time
94        if let Ok(mut last) = self.last_input.lock() {
95            *last = Some(Instant::now());
96        }
97
98        // Handle bracketed paste mode
99        if self.options.handle_bracketed_paste {
100            self.process_with_bracketed_paste(data);
101        } else {
102            self.process_simple(data);
103        }
104    }
105
106    /// Process data with bracketed paste handling.
107    fn process_with_bracketed_paste(&self, data: &str) {
108        let mut remaining = data;
109
110        while !remaining.is_empty() {
111            let in_paste = *self.in_paste.lock().unwrap();
112
113            if in_paste {
114                // Look for paste end marker
115                if let Some(end_pos) = remaining.find("\x1b[201~") {
116                    // Extract paste content
117                    let paste_content = &remaining[..end_pos];
118                    if let Ok(mut paste_buffer) = self.paste_buffer.lock() {
119                        paste_buffer.push_str(paste_content);
120                        let complete_paste = paste_buffer.clone();
121                        paste_buffer.clear();
122
123                        // Emit paste end event
124                        self.emit_event(StdinBufferEvent::PasteEnd(complete_paste));
125                    }
126                    if let Ok(mut in_paste) = self.in_paste.lock() {
127                        *in_paste = false;
128                    }
129                    remaining = &remaining[end_pos + 6..]; // Skip paste end marker
130                } else {
131                    // Still in paste, buffer the data
132                    if let Ok(mut paste_buffer) = self.paste_buffer.lock() {
133                        paste_buffer.push_str(remaining);
134                    }
135                    break;
136                }
137            } else {
138                // Look for paste start marker
139                if let Some(start_pos) = remaining.find("\x1b[200~") {
140                    // Process any data before paste start
141                    if start_pos > 0 {
142                        self.process_simple(&remaining[..start_pos]);
143                    }
144
145                    // Enter paste mode
146                    if let Ok(mut in_paste) = self.in_paste.lock() {
147                        *in_paste = true;
148                    }
149                    self.emit_event(StdinBufferEvent::PasteStart);
150
151                    remaining = &remaining[start_pos + 6..]; // Skip paste start marker
152                } else {
153                    // No paste markers, process as normal
154                    self.process_simple(remaining);
155                    break;
156                }
157            }
158        }
159    }
160
161    /// Process data without bracketed paste handling.
162    fn process_simple(&self, data: &str) {
163        // Check if we should batch
164        let should_batch = self.should_batch();
165
166        if should_batch {
167            // Add to buffer
168            if let Ok(mut buffer) = self.buffer.lock() {
169                if buffer.len() + data.len() <= self.options.max_buffer_size {
170                    buffer.push_str(data);
171                } else {
172                    // Buffer full, flush first
173                    let flushed = std::mem::take(&mut *buffer);
174                    self.emit_event(StdinBufferEvent::Batch(flushed));
175                    buffer.push_str(data);
176                }
177            }
178        } else {
179            // Flush any existing buffer first
180            self.flush();
181
182            // Emit directly
183            self.emit_event(StdinBufferEvent::Data(data.to_string()));
184        }
185    }
186
187    /// Check if we should batch input.
188    fn should_batch(&self) -> bool {
189        if let Ok(last) = self.last_input.lock() {
190            if let Some(last_time) = *last {
191                let elapsed = last_time.elapsed();
192                // Don't batch if this is the first input (elapsed < 1ms)
193                if elapsed < Duration::from_millis(1) {
194                    return false;
195                }
196                return elapsed < Duration::from_millis(self.options.batch_timeout_ms);
197            }
198        }
199        false
200    }
201
202    /// Flush the buffer.
203    pub fn flush(&self) {
204        if let Ok(mut buffer) = self.buffer.lock() {
205            if !buffer.is_empty() {
206                let data = std::mem::take(&mut *buffer);
207                self.emit_event(StdinBufferEvent::Batch(data));
208            }
209        }
210    }
211
212    /// Check if we're in paste mode.
213    pub fn is_in_paste(&self) -> bool {
214        *self.in_paste.lock().unwrap()
215    }
216
217    /// Emit an event to all callbacks.
218    fn emit_event(&self, event: StdinBufferEvent) {
219        if let Ok(callbacks) = self.callbacks.lock() {
220            for callback in callbacks.iter() {
221                callback(&event);
222            }
223        }
224
225        // Also add to event queue
226        if let Ok(mut events) = self.events.lock() {
227            events.push_back(event);
228        }
229    }
230
231    /// Get pending events.
232    pub fn pop_event(&self) -> Option<StdinBufferEvent> {
233        self.events.lock().ok()?.pop_front()
234    }
235
236    /// Clear all pending events.
237    pub fn clear_events(&self) {
238        if let Ok(mut events) = self.events.lock() {
239            events.clear();
240        }
241    }
242
243    /// Reset the buffer state.
244    pub fn reset(&self) {
245        if let Ok(mut buffer) = self.buffer.lock() {
246            buffer.clear();
247        }
248        if let Ok(mut in_paste) = self.in_paste.lock() {
249            *in_paste = false;
250        }
251        if let Ok(mut paste_buffer) = self.paste_buffer.lock() {
252            paste_buffer.clear();
253        }
254        if let Ok(mut last_input) = self.last_input.lock() {
255            *last_input = None;
256        }
257        self.clear_events();
258    }
259}
260
261impl Default for StdinBuffer {
262    fn default() -> Self {
263        Self::new()
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn test_simple_input() {
273        let buffer = StdinBuffer::new();
274        let received = Arc::new(Mutex::new(String::new()));
275        let received_clone = received.clone();
276
277        buffer.on_event(Arc::new(move |event| {
278            if let StdinBufferEvent::Data(data) = event {
279                *received_clone.lock().unwrap() = data.clone();
280            }
281        }));
282
283        buffer.process("hello");
284        assert_eq!(*received.lock().unwrap(), "hello");
285    }
286
287    #[test]
288    fn test_bracketed_paste() {
289        let buffer = StdinBuffer::new();
290        let received = Arc::new(Mutex::new(String::new()));
291        let received_clone = received.clone();
292
293        buffer.on_event(Arc::new(move |event| {
294            if let StdinBufferEvent::PasteEnd(data) = event {
295                *received_clone.lock().unwrap() = data.clone();
296            }
297        }));
298
299        buffer.process("\x1b[200~pasted content\x1b[201~");
300        assert_eq!(*received.lock().unwrap(), "pasted content");
301    }
302
303    #[test]
304    fn test_reset() {
305        let buffer = StdinBuffer::new();
306        buffer.process("data");
307        buffer.reset();
308
309        assert!(!buffer.is_in_paste());
310    }
311}