1use std::io::{self, Write};
50use std::sync::{
51 atomic::{AtomicUsize, Ordering},
52 Arc, Mutex,
53};
54use std::time::Instant;
55
56use crate::console::{ConsoleOptions, DynRenderable, Renderable};
57use crate::segment::Segment;
58
59#[derive(Default)]
61pub struct LiveWriter {
62 buffer: Vec<u8>,
63}
64
65impl LiveWriter {
66 pub fn new() -> Self {
68 Self { buffer: Vec::new() }
69 }
70
71 pub fn capture(&self) -> &[u8] {
73 &self.buffer
74 }
75
76 pub fn clear(&mut self) {
78 self.buffer.clear();
79 }
80}
81
82impl Write for LiveWriter {
83 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
84 self.buffer.extend_from_slice(buf);
85 Ok(buf.len())
86 }
87
88 fn flush(&mut self) -> io::Result<()> {
89 Ok(())
90 }
91}
92
93type RenderHookFn = dyn Fn(&[Vec<Segment>]) -> Vec<Vec<Segment>> + Send;
114
115pub struct RenderHook {
116 hook: Box<RenderHookFn>,
117}
118
119impl RenderHook {
120 pub fn new<F>(hook: F) -> Self
125 where
126 F: Fn(&[Vec<Segment>]) -> Vec<Vec<Segment>> + Send + 'static,
127 {
128 Self {
129 hook: Box::new(hook),
130 }
131 }
132
133 pub fn apply(&self, segments: &[Vec<Segment>]) -> Vec<Vec<Segment>> {
135 (self.hook)(segments)
136 }
137}
138
139pub struct Live {
145 renderable: Arc<Mutex<Option<DynRenderable>>>,
146 screen: bool,
147 auto_refresh: bool,
148 refresh_per_second: f64,
149 transient: bool,
150 started: bool,
151 started_at: Option<Instant>,
152 previous_line_count: Arc<AtomicUsize>,
153 redirect_stdout: bool,
154 redirect_stderr: bool,
155 writers: Arc<Mutex<Vec<LiveWriter>>>,
156 render_hooks: Arc<Mutex<Vec<RenderHook>>>,
157}
158
159impl std::fmt::Debug for Live {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("Live")
162 .field("screen", &self.screen)
163 .field("started", &self.started)
164 .finish()
165 }
166}
167
168impl Live {
169 pub fn new(renderable: impl Renderable + Send + Sync + 'static) -> Self {
171 Self {
172 renderable: Arc::new(Mutex::new(Some(DynRenderable::new(renderable)))),
173 screen: false,
174 auto_refresh: true,
175 refresh_per_second: 4.0,
176 transient: false,
177 started: false,
178 started_at: None,
179 previous_line_count: Arc::new(AtomicUsize::new(0)),
180 redirect_stdout: true,
181 redirect_stderr: true,
182 writers: Arc::new(Mutex::new(Vec::new())),
183 render_hooks: Arc::new(Mutex::new(Vec::new())),
184 }
185 }
186
187 pub fn screen(mut self) -> Self {
189 self.screen = true;
190 self
191 }
192 pub fn no_auto_refresh(mut self) -> Self {
194 self.auto_refresh = false;
195 self
196 }
197 pub fn refresh_per_second(mut self, rate: f64) -> Self {
199 self.refresh_per_second = rate;
200 self
201 }
202 pub fn transient(mut self) -> Self {
204 self.transient = true;
205 self
206 }
207 pub fn redirect_stdout(mut self, redirect: bool) -> Self {
209 self.redirect_stdout = redirect;
210 self
211 }
212 pub fn redirect_stderr(mut self, redirect: bool) -> Self {
214 self.redirect_stderr = redirect;
215 self
216 }
217
218 pub fn add_writer(&mut self, writer: LiveWriter) {
220 self.writers.lock().unwrap().push(writer);
221 }
222
223 pub fn create_writer() -> LiveWriter {
225 LiveWriter::new()
226 }
227
228 pub fn start(&mut self) -> io::Result<()> {
230 self.started = true;
231 self.started_at = Some(Instant::now());
232 if self.screen {
233 write!(io::stdout(), "{}", crate::control::ALT_SCREEN_ENTER)?;
234 }
235 write!(io::stdout(), "{}", crate::control::CURSOR_HIDE)?;
236 self.refresh()
237 }
238
239 pub fn stop(&mut self) -> io::Result<()> {
241 if self.transient {
242 let prev = self.previous_line_count.load(Ordering::Relaxed);
243 for _ in 0..prev {
244 write!(
245 io::stdout(),
246 "{}{}",
247 crate::control::CURSOR_UP,
248 crate::control::ERASE_LINE
249 )?;
250 }
251 }
252 if self.screen {
253 write!(io::stdout(), "{}", crate::control::ALT_SCREEN_EXIT)?;
254 }
255 write!(io::stdout(), "{}", crate::control::CURSOR_SHOW)?;
256 io::stdout().flush()?;
257 self.started = false;
258 self.started_at = None;
259 Ok(())
260 }
261
262 pub fn update(
264 &mut self,
265 renderable: impl Renderable + Send + Sync + 'static,
266 ) -> io::Result<()> {
267 *self.renderable.lock().unwrap() = Some(DynRenderable::new(renderable));
268 self.refresh()
269 }
270
271 pub fn refresh(&mut self) -> io::Result<()> {
276 let renderable_guard = self.renderable.lock().unwrap();
277 if let Some(ref renderable) = *renderable_guard {
278 let opts = ConsoleOptions::default();
279 let result = renderable.render(&opts);
280
281 let prev_lines = self.previous_line_count.load(Ordering::Relaxed);
282 if prev_lines > 0 {
283 write!(io::stdout(), "\x1b[{}F", prev_lines)?;
285 }
286 drop(renderable_guard);
287
288 let hooks_guard = self.render_hooks.lock().unwrap();
290 let (ansi, line_count) = if !hooks_guard.is_empty() {
291 let mut lines = result.lines.clone();
292 for hook in hooks_guard.iter() {
293 lines = hook.apply(&lines);
294 }
295 let mut out = String::new();
296 for line in &lines {
297 for seg in line {
298 out.push_str(&seg.to_ansi());
299 }
300 }
301 (out, lines.len())
302 } else {
303 let s = result.to_ansi();
304 let c = s.lines().count();
305 (s, c)
306 };
307 drop(hooks_guard);
308
309 write!(io::stdout(), "{ansi}")?;
310 if line_count < prev_lines {
311 for _ in line_count..prev_lines {
312 writeln!(io::stdout(), "{}", crate::control::ERASE_LINE)?;
313 }
314 }
315
316 self.previous_line_count
317 .store(line_count, Ordering::Relaxed);
318
319 let writers_guard = self.writers.lock().unwrap();
321 for writer in writers_guard.iter() {
322 let captured = String::from_utf8_lossy(writer.capture());
323 if !captured.is_empty() {
324 write!(io::stdout(), "{}", captured)?;
325 }
326 }
327
328 io::stdout().flush()?;
329 }
330 Ok(())
331 }
332
333 pub fn is_started(&self) -> bool {
335 self.started
336 }
337
338 pub fn get_renderable(&self) -> Option<DynRenderable> {
340 self.renderable.lock().unwrap().clone()
341 }
342
343 pub fn renderable(&self) -> Option<DynRenderable> {
345 self.renderable.lock().unwrap().clone()
346 }
347
348 pub fn process_renderables(
353 &self,
354 renderables: &[Box<dyn Renderable>],
355 options: &ConsoleOptions,
356 ) -> Vec<Vec<Segment>> {
357 let mut all_lines = Vec::new();
358 for renderable in renderables {
359 let result = renderable.render(options);
360 all_lines.extend(result.lines);
361 }
362 all_lines
363 }
364
365 pub fn add_render_hook(&mut self, hook: RenderHook) {
370 self.render_hooks.lock().unwrap().push(hook);
371 }
372}
373
374impl Drop for Live {
375 fn drop(&mut self) {
376 let _ = self.stop();
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383 use crate::text::Text;
384
385 #[test]
386 fn test_is_started() {
387 let mut live = Live::new(Text::new("test"));
388 assert!(!live.is_started());
389 live.start().unwrap();
390 assert!(live.is_started());
391 live.stop().unwrap();
392 assert!(!live.is_started());
393 }
394
395 #[test]
396 fn test_renderable_accessor() {
397 let live = Live::new(Text::new("hello"));
398 let r = live.get_renderable().expect("renderable should be set");
399 let opts = ConsoleOptions::default();
401 let result = r.render(&opts);
402 assert!(!result.to_ansi().is_empty());
403 }
404
405 #[test]
406 fn test_render_hook_basic() {
407 let hook = RenderHook::new(|segments| segments.to_vec());
408 let input = vec![vec![Segment::new("test")]];
409 let output = hook.apply(&input);
410 assert_eq!(output.len(), 1);
411 assert_eq!(output[0][0].text, "test");
412 }
413
414 #[test]
415 fn test_render_hook_transform() {
416 let hook = RenderHook::new(|segments| {
417 let mut transformed = segments.to_vec();
418 transformed.push(vec![Segment::new("appended")]);
419 transformed
420 });
421 let input = vec![vec![Segment::new("original")]];
422 let output = hook.apply(&input);
423 assert_eq!(output.len(), 2);
424 assert_eq!(output[1][0].text, "appended");
425 }
426
427 #[test]
428 fn test_process_renderables() {
429 let live = Live::new(Text::new("dummy"));
430 let opts = ConsoleOptions::default();
431 let renderables: Vec<Box<dyn Renderable>> =
432 vec![Box::new(Text::new("first")), Box::new(Text::new("second"))];
433 let lines = live.process_renderables(&renderables, &opts);
434 assert!(!lines.is_empty());
435 }
436
437 #[test]
438 fn test_start_stop_cycle() {
439 let mut live = Live::new(Text::new("test"));
440 assert!(!live.is_started());
441 live.start().unwrap();
442 assert!(live.is_started());
443 live.stop().unwrap();
444 assert!(!live.is_started());
445 }
446
447 #[test]
448 fn test_add_render_hook() {
449 let mut live = Live::new(Text::new("test"));
450 let hook = RenderHook::new(|segments| segments.to_vec());
451 live.add_render_hook(hook);
452 assert_eq!(live.render_hooks.lock().unwrap().len(), 1);
453 }
454}