use std::io::{self, Write};
use crossterm::cursor::{MoveToColumn, MoveUp};
use crossterm::queue;
use crossterm::style::Print;
use crossterm::terminal::{Clear, ClearType};
use crate::core::cursor;
use crate::core::render::{Renderable, Rendered, write_line};
use crate::core::terminal::{color_support, is_output_tty, term_width};
use crate::error::Result;
pub(crate) struct InPlace {
interactive: bool,
silent: bool,
last_height: u16,
last_frame: Rendered,
}
impl InPlace {
pub(crate) fn new(always: bool) -> Self {
Self {
interactive: always || is_output_tty(),
silent: false,
last_height: 0,
last_frame: Rendered::empty(),
}
}
pub(crate) fn silent() -> Self {
Self {
interactive: false,
silent: true,
last_height: 0,
last_frame: Rendered::empty(),
}
}
pub(crate) fn draw(&mut self, frame: &Rendered) -> Result<()> {
if self.silent {
return Ok(());
}
if !self.interactive {
self.last_frame = frame.clone();
return Ok(());
}
cursor::hide();
let mut out = io::stdout().lock();
self.rewind(&mut out)?;
self.last_height = write_frame(&mut out, frame)?;
out.flush()?;
Ok(())
}
fn rewind<W: Write>(&self, out: &mut W) -> io::Result<()> {
if self.last_height == 0 {
return Ok(());
}
queue!(out, MoveToColumn(0))?;
if self.last_height > 1 {
queue!(out, MoveUp(self.last_height - 1))?;
}
queue!(out, Clear(ClearType::FromCursorDown))?;
Ok(())
}
pub(crate) fn reset(&mut self) {
self.last_height = 0;
}
pub(crate) fn finish(self) -> Result<()> {
if self.silent {
return Ok(());
}
{
let mut out = io::stdout().lock();
if self.interactive {
queue!(out, Print("\r\n"))?;
} else {
write_frame(&mut out, &self.last_frame)?;
queue!(out, Print("\n"))?;
}
out.flush()?;
}
cursor::show();
Ok(())
}
pub(crate) fn clear(mut self) -> Result<()> {
if !self.silent && self.interactive {
{
let mut out = io::stdout().lock();
self.rewind(&mut out)?;
self.last_height = 0;
out.flush()?;
}
cursor::show();
}
Ok(())
}
}
impl Drop for InPlace {
fn drop(&mut self) {
cursor::show();
}
}
fn write_frame<W: Write>(out: &mut W, frame: &Rendered) -> io::Result<u16> {
let support = color_support();
for (index, line) in frame.lines.iter().enumerate() {
if index > 0 {
queue!(out, Print("\r\n"))?;
}
write_line(out, line, support)?;
}
Ok(frame.lines.len() as u16)
}
pub struct Live {
inplace: InPlace,
}
impl Default for Live {
fn default() -> Self {
Self::new()
}
}
impl Live {
pub fn new() -> Self {
Self {
inplace: InPlace::new(false),
}
}
pub fn always() -> Self {
Self {
inplace: InPlace::new(true),
}
}
pub fn update(&mut self, content: &impl Renderable) -> Result<()> {
self.inplace.draw(&content.render(term_width()))
}
pub fn finish(self) -> Result<()> {
self.inplace.finish()
}
pub fn clear(self) -> Result<()> {
self.inplace.clear()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constructs_without_drawing() {
let _live = Live::new();
let _always = Live::always();
}
#[test]
fn silent_writer_never_draws() {
let mut inplace = InPlace::silent();
let frame = Rendered::new(vec![crate::core::text::Line::raw("x")]);
assert!(inplace.draw(&frame).is_ok());
assert!(inplace.finish().is_ok());
}
}