1use std::io::{self, Write};
50use std::time::Instant;
51
52use crate::console::{ConsoleOptions, DynRenderable, Renderable};
53use crate::segment::Segment;
54
55pub struct LiveWriter {
57 buffer: Vec<u8>,
58}
59
60impl LiveWriter {
61 pub fn new() -> Self {
63 Self { buffer: Vec::new() }
64 }
65
66 pub fn capture(&self) -> &[u8] {
68 &self.buffer
69 }
70
71 pub fn clear(&mut self) {
73 self.buffer.clear();
74 }
75}
76
77impl Write for LiveWriter {
78 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
79 self.buffer.extend_from_slice(buf);
80 Ok(buf.len())
81 }
82
83 fn flush(&mut self) -> io::Result<()> {
84 Ok(())
85 }
86}
87
88pub struct RenderHook {
108 hook: Box<dyn Fn(&[Vec<Segment>]) -> Vec<Vec<Segment>> + Send>,
109}
110
111impl RenderHook {
112 pub fn new<F>(hook: F) -> Self
117 where
118 F: Fn(&[Vec<Segment>]) -> Vec<Vec<Segment>> + Send + 'static,
119 {
120 Self {
121 hook: Box::new(hook),
122 }
123 }
124
125 pub fn apply(&self, segments: &[Vec<Segment>]) -> Vec<Vec<Segment>> {
127 (self.hook)(segments)
128 }
129}
130
131pub struct Live {
133 renderable: Option<DynRenderable>,
134 screen: bool,
135 auto_refresh: bool,
136 refresh_per_second: f64,
137 transient: bool,
138 started: bool,
139 started_at: Option<Instant>,
140 previous_line_count: usize,
141 redirect_stdout: bool,
142 redirect_stderr: bool,
143 writers: Vec<LiveWriter>,
144 render_hooks: Vec<RenderHook>,
145}
146
147impl std::fmt::Debug for Live {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 f.debug_struct("Live")
150 .field("screen", &self.screen)
151 .field("started", &self.started)
152 .finish()
153 }
154}
155
156impl Live {
157 pub fn new(renderable: impl Renderable + Send + Sync + 'static) -> Self {
159 Self {
160 renderable: Some(DynRenderable::new(renderable)),
161 screen: false,
162 auto_refresh: true,
163 refresh_per_second: 4.0,
164 transient: false,
165 started: false,
166 started_at: None,
167 previous_line_count: 0,
168 redirect_stdout: true,
169 redirect_stderr: true,
170 writers: Vec::new(),
171 render_hooks: Vec::new(),
172 }
173 }
174
175 pub fn screen(mut self) -> Self { self.screen = true; self }
177 pub fn no_auto_refresh(mut self) -> Self { self.auto_refresh = false; self }
179 pub fn refresh_per_second(mut self, rate: f64) -> Self { self.refresh_per_second = rate; self }
181 pub fn transient(mut self) -> Self { self.transient = true; self }
183 pub fn redirect_stdout(mut self, redirect: bool) -> Self { self.redirect_stdout = redirect; self }
185 pub fn redirect_stderr(mut self, redirect: bool) -> Self { self.redirect_stderr = redirect; self }
187
188 pub fn add_writer(&mut self, writer: LiveWriter) { self.writers.push(writer); }
190
191 pub fn create_writer() -> LiveWriter {
193 LiveWriter::new()
194 }
195
196 pub fn start(&mut self) -> io::Result<()> {
198 self.started = true;
199 self.started_at = Some(Instant::now());
200 if self.screen {
201 write!(io::stdout(), "\x1b[?1049h")?;
202 }
203 write!(io::stdout(), "\x1b[?25l")?;
204 self.refresh()
205 }
206
207 pub fn stop(&mut self) -> io::Result<()> {
209 if self.transient {
210 for _ in 0..self.previous_line_count {
211 write!(io::stdout(), "\x1b[1A\x1b[2K")?;
212 }
213 }
214 if self.screen {
215 write!(io::stdout(), "\x1b[?1049l")?;
216 }
217 write!(io::stdout(), "\x1b[?25h")?;
218 io::stdout().flush()?;
219 self.started = false;
220 self.started_at = None;
221 Ok(())
222 }
223
224 pub fn update(&mut self, renderable: impl Renderable + Send + Sync + 'static) -> io::Result<()> {
226 self.renderable = Some(DynRenderable::new(renderable));
227 self.refresh()
228 }
229
230 pub fn refresh(&mut self) -> io::Result<()> {
235 if let Some(ref renderable) = self.renderable {
236 let opts = ConsoleOptions::default();
237 let result = renderable.render(&opts);
238
239 if self.previous_line_count > 0 {
240 write!(io::stdout(), "\x1b[{}F", self.previous_line_count)?;
241 }
242
243 let (ansi, line_count) = if !self.render_hooks.is_empty() {
245 let mut lines = result.lines.clone();
246 for hook in &self.render_hooks {
247 lines = hook.apply(&lines);
248 }
249 let mut out = String::new();
250 for line in &lines {
251 for seg in line {
252 out.push_str(&seg.to_ansi());
253 }
254 }
255 (out, lines.len())
256 } else {
257 let s = result.to_ansi();
258 let c = s.lines().count();
259 (s, c)
260 };
261
262 write!(io::stdout(), "{ansi}")?;
263 if line_count < self.previous_line_count {
264 for _ in line_count..self.previous_line_count {
265 write!(io::stdout(), "\x1b[2K\n")?;
266 }
267 }
268
269 self.previous_line_count = line_count;
270
271 for writer in &self.writers {
273 let captured = String::from_utf8_lossy(writer.capture());
274 if !captured.is_empty() {
275 write!(io::stdout(), "{}", captured)?;
276 }
277 }
278
279 io::stdout().flush()?;
280 }
281 Ok(())
282 }
283
284 pub fn is_started(&self) -> bool {
286 self.started
287 }
288
289 pub fn get_renderable(&self) -> &dyn Renderable {
296 self.renderable.as_ref().unwrap() as &dyn Renderable
297 }
298
299 pub fn renderable(&self) -> &dyn Renderable {
306 self.renderable.as_ref().unwrap() as &dyn Renderable
307 }
308
309 pub fn process_renderables(
314 &self,
315 renderables: &[Box<dyn Renderable>],
316 options: &ConsoleOptions,
317 ) -> Vec<Vec<Segment>> {
318 let mut all_lines = Vec::new();
319 for renderable in renderables {
320 let result = renderable.render(options);
321 all_lines.extend(result.lines);
322 }
323 all_lines
324 }
325
326 pub fn add_render_hook(&mut self, hook: RenderHook) {
331 self.render_hooks.push(hook);
332 }
333}
334
335impl Drop for Live {
336 fn drop(&mut self) {
337 let _ = self.stop();
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use crate::text::Text;
345
346 #[test]
347 fn test_is_started() {
348 let mut live = Live::new(Text::new("test"));
349 assert!(!live.is_started());
350 live.start().unwrap();
351 assert!(live.is_started());
352 live.stop().unwrap();
353 assert!(!live.is_started());
354 }
355
356 #[test]
357 fn test_renderable_accessor() {
358 let live = Live::new(Text::new("hello"));
359 let r = live.get_renderable();
360 let opts = ConsoleOptions::default();
362 let result = r.render(&opts);
363 assert!(!result.to_ansi().is_empty());
364 }
365
366 #[test]
367 fn test_render_hook_basic() {
368 let hook = RenderHook::new(|segments| segments.to_vec());
369 let input = vec![vec![Segment::new("test")]];
370 let output = hook.apply(&input);
371 assert_eq!(output.len(), 1);
372 assert_eq!(output[0][0].text, "test");
373 }
374
375 #[test]
376 fn test_render_hook_transform() {
377 let hook = RenderHook::new(|segments| {
378 let mut transformed = segments.to_vec();
379 transformed.push(vec![Segment::new("appended")]);
380 transformed
381 });
382 let input = vec![vec![Segment::new("original")]];
383 let output = hook.apply(&input);
384 assert_eq!(output.len(), 2);
385 assert_eq!(output[1][0].text, "appended");
386 }
387
388 #[test]
389 fn test_process_renderables() {
390 let live = Live::new(Text::new("dummy"));
391 let opts = ConsoleOptions::default();
392 let renderables: Vec<Box<dyn Renderable>> = vec![
393 Box::new(Text::new("first")),
394 Box::new(Text::new("second")),
395 ];
396 let lines = live.process_renderables(&renderables, &opts);
397 assert!(!lines.is_empty());
398 }
399
400 #[test]
401 fn test_start_stop_cycle() {
402 let mut live = Live::new(Text::new("test"));
403 assert!(!live.is_started());
404 live.start().unwrap();
405 assert!(live.is_started());
406 live.stop().unwrap();
407 assert!(!live.is_started());
408 }
409
410 #[test]
411 fn test_add_render_hook() {
412 let mut live = Live::new(Text::new("test"));
413 let hook = RenderHook::new(|segments| segments.to_vec());
414 live.add_render_hook(hook);
415 assert_eq!(live.render_hooks.len(), 1);
416 }
417}