use std::cell::Cell;
use crate::console::{Console, ConsoleOptions};
use crate::control::{Control, ControlType};
use crate::protocol::Renderable;
use crate::segment::Segment;
use crate::style::Style;
pub struct LiveRender {
renderable: Box<dyn Renderable>,
style: Option<Style>,
shape: Cell<Option<(usize, usize)>>,
}
impl LiveRender {
pub fn new(renderable: Box<dyn Renderable>) -> Self {
LiveRender {
renderable,
style: None,
shape: Cell::new(None),
}
}
pub fn style(mut self, style: Style) -> Self {
self.style = Some(style);
self
}
pub fn set_renderable(&mut self, renderable: Box<dyn Renderable>) {
self.renderable = renderable;
}
pub fn position_cursor(&self) -> Control {
match self.shape.get() {
Some((_, height)) => {
let mut codes = vec![ControlType::CarriageReturn, ControlType::EraseInLine(2)];
for _ in 0..height.saturating_sub(1) {
codes.push(ControlType::CursorUp(1));
codes.push(ControlType::EraseInLine(2));
}
Control::new(&codes)
}
None => Control::new(&[]),
}
}
pub fn restore_cursor(&self) -> Control {
match self.shape.get() {
Some((_, height)) => {
let mut codes = vec![ControlType::CarriageReturn];
for _ in 0..height {
codes.push(ControlType::CursorUp(1));
codes.push(ControlType::EraseInLine(2));
}
Control::new(&codes)
}
None => Control::new(&[]),
}
}
}
impl Renderable for LiveRender {
fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
let mut lines = console.render_lines(self.renderable.as_ref(), options, false);
if let Some(style) = &self.style {
for line in &mut lines {
*line = Segment::apply_style(line, style);
}
}
let height = lines.len();
let width = lines
.iter()
.map(|line| line.iter().map(Segment::cell_length).sum::<usize>())
.max()
.unwrap_or(0);
self.shape.set(Some((width, height)));
let mut segments = Vec::new();
let last = height.saturating_sub(1);
for (index, line) in lines.into_iter().enumerate() {
segments.extend(line);
if index != last {
segments.push(Segment::line());
}
}
segments
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::ColorSystem;
use crate::text::Text;
fn console() -> Console {
Console::builder()
.force_terminal(true)
.color_system(Some(ColorSystem::Truecolor))
.width(20)
.no_color(false)
.build()
}
#[test]
fn renders_content_and_control_codes() {
let live = LiveRender::new(Box::new(Text::new("line one\nline two\nline three")));
let console = console();
assert_eq!(
console.render_to_string(&live),
"line one\nline two\nline three"
);
assert_eq!(
live.position_cursor().as_str(),
"\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
);
assert_eq!(
live.restore_cursor().as_str(),
"\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
);
}
#[test]
fn single_line_shape() {
let live = LiveRender::new(Box::new(Text::new("solo")));
let console = console();
assert_eq!(console.render_to_string(&live), "solo");
assert_eq!(live.position_cursor().as_str(), "\r\x1b[2K");
assert_eq!(live.restore_cursor().as_str(), "\r\x1b[1A\x1b[2K");
}
#[test]
fn no_control_before_first_render() {
let live = LiveRender::new(Box::new(Text::new("x")));
assert_eq!(live.position_cursor().as_str(), "");
assert_eq!(live.restore_cursor().as_str(), "");
}
}