use std::io::{self, BufWriter, Stdout, Write};
use crossterm::{cursor, event, execute, queue, style, terminal};
use crate::backend::Backend;
use crate::backend::ansi::to_ct;
use crate::buffer::CellUpdate;
pub struct FullscreenBackend {
out: BufWriter<Stdout>,
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl FullscreenBackend {
pub fn new() -> Self {
FullscreenBackend {
out: BufWriter::new(io::stdout()),
}
}
}
impl Default for FullscreenBackend {
#[cfg_attr(coverage_nightly, coverage(off))]
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl Backend for FullscreenBackend {
fn size(&self) -> io::Result<(u16, u16)> {
terminal::size()
}
fn enter(&mut self) -> io::Result<()> {
terminal::enable_raw_mode()?;
execute!(
self.out,
terminal::EnterAlternateScreen,
cursor::Hide,
terminal::Clear(terminal::ClearType::All),
event::EnableBracketedPaste
)
.inspect_err(|_| {
let _ = terminal::disable_raw_mode();
let _ = execute!(
self.out,
terminal::LeaveAlternateScreen,
cursor::Show,
event::DisableBracketedPaste
);
})
}
fn leave(&mut self) -> io::Result<()> {
execute!(
self.out,
cursor::Show,
terminal::LeaveAlternateScreen,
event::DisableBracketedPaste
)?;
terminal::disable_raw_mode()
}
fn flush(&mut self, updates: &[CellUpdate]) -> io::Result<()> {
write_coalesced_updates(&mut self.out, updates)?;
self.out.flush()
}
fn copy_to_clipboard(&mut self, text: &str) -> io::Result<()> {
write!(
self.out,
"{}",
crate::backend::ansi::osc52_copy_payload(text)
)?;
self.out.flush()
}
}
fn write_coalesced_updates(out: &mut impl Write, updates: &[CellUpdate]) -> io::Result<()> {
let mut i = 0;
while i < updates.len() {
let start = &updates[i];
let mut run = String::new();
run.push(start.cell.ch);
let mut j = i + 1;
while j < updates.len() {
let prev = &updates[j - 1];
let cur = &updates[j];
let contiguous = cur.y == prev.y && cur.x == prev.x + 1;
let same_style = cur.cell.fg == start.cell.fg
&& cur.cell.bg == start.cell.bg
&& cur.cell.attrs == start.cell.attrs;
if !contiguous || !same_style {
break;
}
run.push(cur.cell.ch);
j += 1;
}
let attrs = crate::backend::ansi::ct_attrs(start.cell.attrs);
queue!(
out,
cursor::MoveTo(start.x, start.y),
style::SetAttribute(style::Attribute::Reset),
style::SetAttributes(attrs),
style::SetForegroundColor(to_ct(start.cell.fg)),
style::SetBackgroundColor(to_ct(start.cell.bg)),
style::Print(run),
)?;
i = j;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::buffer::Cell;
use crate::style::{Attrs, Color};
fn update(x: u16, y: u16, ch: char, fg: Color, bg: Color, attrs: Attrs) -> CellUpdate {
CellUpdate {
x,
y,
cell: Cell { ch, fg, bg, attrs },
}
}
#[test]
fn merges_two_contiguous_same_styled_updates_into_one_print() {
let mut out = Vec::new();
let updates = [
update(0, 0, 'h', Color::Red, Color::Reset, Attrs::default()),
update(1, 0, 'i', Color::Red, Color::Reset, Attrs::default()),
];
write_coalesced_updates(&mut out, &updates).unwrap();
let s = String::from_utf8(out).unwrap();
assert!(s.contains("hi"));
assert_eq!(s.matches("\u{1b}[1;1H").count(), 1);
}
#[test]
fn style_change_between_adjacent_updates_breaks_the_run() {
let mut out = Vec::new();
let updates = [
update(0, 0, 'h', Color::Red, Color::Reset, Attrs::default()),
update(1, 0, 'i', Color::Blue, Color::Reset, Attrs::default()),
];
write_coalesced_updates(&mut out, &updates).unwrap();
let s = String::from_utf8(out).unwrap();
assert!(s.contains('h') && s.contains('i'));
assert!(!s.contains("hi"));
assert_eq!(s.matches("\u{1b}[1;1H").count(), 1);
assert_eq!(s.matches("\u{1b}[1;2H").count(), 1);
}
#[test]
fn row_change_breaks_the_run() {
let mut out = Vec::new();
let updates = [
update(0, 0, 'h', Color::Red, Color::Reset, Attrs::default()),
update(1, 1, 'i', Color::Red, Color::Reset, Attrs::default()),
];
write_coalesced_updates(&mut out, &updates).unwrap();
let s = String::from_utf8(out).unwrap();
assert!(s.contains('h') && s.contains('i'));
assert!(!s.contains("hi"));
assert_eq!(s.matches("\u{1b}[1;1H").count(), 1);
assert_eq!(s.matches("\u{1b}[2;2H").count(), 1);
}
#[test]
fn non_contiguous_x_gap_breaks_the_run() {
let mut out = Vec::new();
let updates = [
update(0, 0, 'h', Color::Red, Color::Reset, Attrs::default()),
update(2, 0, 'i', Color::Red, Color::Reset, Attrs::default()),
];
write_coalesced_updates(&mut out, &updates).unwrap();
let s = String::from_utf8(out).unwrap();
assert!(s.contains('h') && s.contains('i'));
assert!(!s.contains("hi"));
assert_eq!(s.matches("\u{1b}[1;1H").count(), 1);
assert_eq!(s.matches("\u{1b}[1;3H").count(), 1);
}
}