use crossterm::{
cursor, queue,
style::Print,
terminal::{ClearType, size},
};
use std::io::{Write, stdout};
pub struct BufferWriter {
buffer: String,
line_count: usize,
}
impl Default for BufferWriter {
fn default() -> Self {
Self::new()
}
}
impl BufferWriter {
pub fn new() -> Self {
Self {
buffer: String::with_capacity(64 * 1024),
line_count: 0,
}
}
#[allow(dead_code)] pub fn reset(&mut self) {
self.buffer.clear();
self.line_count = 0;
}
pub fn get_buffer(&self) -> &str {
&self.buffer
}
pub fn line_count(&self) -> usize {
self.line_count
}
}
impl Write for BufferWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let s = std::str::from_utf8(buf)
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid UTF-8"))?;
self.line_count += s.matches('\n').count();
self.buffer.push_str(s);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
pub struct DifferentialRenderer {
previous_lines: Vec<String>,
screen_height: usize,
screen_width: usize,
write_buf: Vec<u8>,
}
impl DifferentialRenderer {
pub fn new() -> std::io::Result<Self> {
let (width, height) = size().unwrap_or((80, 24));
Ok(Self {
previous_lines: Vec::new(),
screen_height: height as usize,
screen_width: width as usize,
write_buf: Vec::with_capacity(32 * 1024),
})
}
pub fn update_dimensions(&mut self, width: u16, height: u16) {
let w = width as usize;
let h = height as usize;
if w != self.screen_width || h != self.screen_height {
self.screen_width = w;
self.screen_height = h;
self.previous_lines.resize(h, String::new());
}
}
pub fn render_differential(
&mut self,
content: &str,
cols: u16,
rows: u16,
) -> std::io::Result<()> {
self.update_dimensions(cols, rows);
if self.previous_lines.is_empty() {
self.previous_lines = vec![String::new(); self.screen_height];
}
self.write_buf.clear();
let mut any_changes = false;
let mut current_line_count = 0;
for (line_num, current_line) in content.lines().enumerate() {
if line_num >= self.screen_height {
break;
}
current_line_count = line_num + 1;
if self.previous_lines[line_num] != current_line {
queue!(
self.write_buf,
cursor::MoveTo(0, line_num as u16),
crossterm::terminal::Clear(ClearType::UntilNewLine),
Print(current_line)
)?;
self.previous_lines[line_num].clear();
self.previous_lines[line_num].push_str(current_line);
any_changes = true;
}
}
for line_num in current_line_count..self.screen_height {
if !self.previous_lines[line_num].is_empty() {
queue!(
self.write_buf,
cursor::MoveTo(0, line_num as u16),
crossterm::terminal::Clear(ClearType::CurrentLine)
)?;
self.previous_lines[line_num].clear();
any_changes = true;
}
}
if any_changes {
let mut stdout = stdout();
stdout.write_all(&self.write_buf)?;
stdout.flush()?;
}
Ok(())
}
pub fn force_clear(&mut self) -> std::io::Result<()> {
let mut stdout = stdout();
queue!(stdout, crossterm::terminal::Clear(ClearType::All))?;
stdout.flush()?;
self.previous_lines.clear();
self.previous_lines
.resize(self.screen_height, String::new());
Ok(())
}
}