use std::io::{self, BufWriter, Stdout, Write};
use crossterm::{cursor, execute, queue, terminal};
use crate::backend::ansi::write_row;
use crate::buffer::Cell;
pub(crate) trait InlineSink {
fn size(&self) -> io::Result<(u16, u16)>;
fn enter(&mut self) -> io::Result<()>;
fn leave(&mut self) -> io::Result<()>;
fn commit(&mut self, rows: &[Vec<Cell>]) -> io::Result<()>;
fn present(&mut self, rows: &[Vec<Cell>]) -> io::Result<()>;
fn copy_to_clipboard(&mut self, _text: &str) -> io::Result<()> {
Ok(())
}
}
pub struct InlineBackend {
out: BufWriter<Stdout>,
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl InlineBackend {
pub fn new() -> Self {
InlineBackend {
out: BufWriter::new(io::stdout()),
}
}
}
impl Default for InlineBackend {
#[cfg_attr(coverage_nightly, coverage(off))]
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl InlineSink for InlineBackend {
fn size(&self) -> io::Result<(u16, u16)> {
terminal::size()
}
fn enter(&mut self) -> io::Result<()> {
terminal::enable_raw_mode()?;
execute!(
self.out,
cursor::Hide,
cursor::MoveToColumn(0),
crossterm::event::EnableBracketedPaste
)
.inspect_err(|_| {
let _ = terminal::disable_raw_mode();
let _ = execute!(
self.out,
cursor::Show,
crossterm::event::DisableBracketedPaste
);
})
}
fn leave(&mut self) -> io::Result<()> {
execute!(
self.out,
terminal::Clear(terminal::ClearType::FromCursorDown),
cursor::Show,
crossterm::event::DisableBracketedPaste,
)?;
terminal::disable_raw_mode()
}
fn commit(&mut self, rows: &[Vec<Cell>]) -> io::Result<()> {
let r = (|| -> io::Result<()> {
queue!(
self.out,
terminal::Clear(terminal::ClearType::FromCursorDown)
)?;
for row in rows {
write_row(&mut self.out, row)?;
queue!(self.out, crossterm::style::Print("\r\n"))?;
}
self.out.flush()
})();
if r.is_err() {
let _ = self.out.flush();
}
r
}
fn present(&mut self, rows: &[Vec<Cell>]) -> io::Result<()> {
let r = (|| -> io::Result<()> {
queue!(
self.out,
terminal::Clear(terminal::ClearType::FromCursorDown)
)?;
for (i, row) in rows.iter().enumerate() {
write_row(&mut self.out, row)?;
if i + 1 < rows.len() {
queue!(self.out, crossterm::style::Print("\r\n"))?;
}
}
if rows.len() > 1 {
queue!(self.out, cursor::MoveUp(rows.len() as u16 - 1))?;
}
queue!(self.out, cursor::MoveToColumn(0))?;
self.out.flush()
})();
if r.is_err() {
let _ = self.out.flush();
}
r
}
fn copy_to_clipboard(&mut self, text: &str) -> io::Result<()> {
write!(
self.out,
"{}",
crate::backend::ansi::osc52_copy_payload(text)
)?;
self.out.flush()
}
}
pub(crate) fn buffer_rows(buf: &crate::buffer::Buffer) -> Vec<Vec<Cell>> {
(0..buf.height())
.map(|y| (0..buf.width()).map(|x| *buf.get(x, y)).collect())
.collect()
}
#[cfg(test)]
pub(crate) struct RecordingSink {
pub committed: Vec<String>,
pub live: Vec<String>,
pub size: (u16, u16),
pub lifecycle: Vec<&'static str>,
}
#[cfg(test)]
impl RecordingSink {
pub fn new(w: u16, h: u16) -> Self {
RecordingSink {
committed: Vec::new(),
live: Vec::new(),
size: (w, h),
lifecycle: Vec::new(),
}
}
fn rows_to_text(rows: &[Vec<Cell>]) -> Vec<String> {
rows.iter()
.map(|r| {
r.iter()
.map(|c| c.ch)
.collect::<String>()
.trim_end()
.to_string()
})
.collect()
}
}
#[cfg(test)]
impl InlineSink for RecordingSink {
fn size(&self) -> io::Result<(u16, u16)> {
Ok(self.size)
}
fn enter(&mut self) -> io::Result<()> {
self.lifecycle.push("enter");
Ok(())
}
fn leave(&mut self) -> io::Result<()> {
self.lifecycle.push("leave");
Ok(())
}
fn commit(&mut self, rows: &[Vec<Cell>]) -> io::Result<()> {
self.committed.extend(Self::rows_to_text(rows));
Ok(())
}
fn present(&mut self, rows: &[Vec<Cell>]) -> io::Result<()> {
self.live = Self::rows_to_text(rows);
Ok(())
}
}