use std::io;
use std::io::Write;
use std::os::unix::io::AsRawFd;
use crate::attr::{Attr, Color, Effect};
use crate::sys::size::terminal_size;
use term::terminfo::parm::{expand, Param, Variables};
use term::terminfo::TermInfo;
const DEFAULT_BUFFER_SIZE: usize = 1024;
pub struct Output {
buffer: Vec<u8>,
stdout: Box<dyn WriteAndAsRawFdAndSend>,
terminfo: TermInfo,
}
pub trait WriteAndAsRawFdAndSend: Write + AsRawFd + Send {}
impl<T> WriteAndAsRawFdAndSend for T where T: Write + AsRawFd + Send {}
impl Output {
pub fn new(stdout: Box<dyn WriteAndAsRawFdAndSend>) -> io::Result<Self> {
Result::Ok(Self {
buffer: Vec::with_capacity(DEFAULT_BUFFER_SIZE),
stdout,
terminfo: TermInfo::from_env()?,
})
}
fn write_cap(&mut self, cmd: &str) {
self.write_cap_with_params(cmd, &[])
}
fn write_cap_with_params(&mut self, cap: &str, params: &[Param]) {
if let Some(cmd) = self.terminfo.strings.get(cap) {
if let Ok(s) = expand(cmd, params, &mut Variables::new()) {
self.buffer.extend(&s);
}
}
}
pub fn write(&mut self, data: &str) {
self.buffer.extend(data.replace("\x1b", "?").as_bytes());
}
pub fn write_raw(&mut self, data: &[u8]) {
self.buffer.extend_from_slice(data);
}
pub fn encoding(&self) -> &str {
unimplemented!()
}
pub fn set_title(&mut self, title: &str) {
if self.terminfo.names.contains(&"linux".to_string())
|| self.terminfo.names.contains(&"eterm-color".to_string())
{
return;
}
let title = title.replace("\x1b", "").replace("\x07", "");
self.write_raw(format!("\x1b]2;{}\x07", title).as_bytes());
}
pub fn clear_title(&mut self) {
self.set_title("");
}
pub fn flush(&mut self) {
let _ = self.stdout.write(&self.buffer);
self.buffer.clear();
let _ = self.stdout.flush();
}
pub fn erase_screen(&mut self) {
self.write_cap("clear");
}
pub fn enter_alternate_screen(&mut self) {
self.write_cap("smcup");
}
pub fn quit_alternate_screen(&mut self) {
self.write_cap("rmcup");
}
pub fn enable_mouse_support(&mut self) {
self.write_raw("\x1b[?1000h".as_bytes());
self.write_raw("\x1b[?1015h".as_bytes());
self.write_raw("\x1b[?1006h".as_bytes());
}
pub fn disable_mouse_support(&mut self) {
self.write_raw("\x1b[?1000l".as_bytes());
self.write_raw("\x1b[?1015l".as_bytes());
self.write_raw("\x1b[?1006l".as_bytes());
}
pub fn erase_end_of_line(&mut self) {
self.write_cap("el");
}
pub fn erase_down(&mut self) {
self.write_cap("ed");
}
pub fn reset_attributes(&mut self) {
self.write_cap("sgr0");
}
pub fn set_fg(&mut self, color: Color) {
match color {
Color::Default => {
self.write_raw("\x1b[39m".as_bytes());
}
Color::AnsiValue(x) => {
self.write_cap_with_params("setaf", &[Param::Number(x as i32)]);
}
Color::Rgb(r, g, b) => {
self.write_raw(format!("\x1b[38;2;{};{};{}m", r, g, b).as_bytes());
}
Color::__Nonexhaustive => unreachable!(),
}
}
pub fn set_bg(&mut self, color: Color) {
match color {
Color::Default => {
self.write_raw("\x1b[49m".as_bytes());
}
Color::AnsiValue(x) => {
self.write_cap_with_params("setab", &[Param::Number(x as i32)]);
}
Color::Rgb(r, g, b) => {
self.write_raw(format!("\x1b[48;2;{};{};{}m", r, g, b).as_bytes());
}
Color::__Nonexhaustive => unreachable!(),
}
}
pub fn set_effect(&mut self, effect: Effect) {
if effect.contains(Effect::BOLD) {
self.write_cap("bold");
}
if effect.contains(Effect::DIM) {
self.write_cap("dim");
}
if effect.contains(Effect::UNDERLINE) {
self.write_cap("smul");
}
if effect.contains(Effect::BLINK) {
self.write_cap("blink");
}
if effect.contains(Effect::REVERSE) {
self.write_cap("rev");
}
}
pub fn set_attribute(&mut self, attr: Attr) {
self.set_fg(attr.fg);
self.set_bg(attr.bg);
self.set_effect(attr.effect);
}
pub fn disable_autowrap(&mut self) {
self.write_cap("rmam");
}
pub fn enable_autowrap(&mut self) {
self.write_cap("smam");
}
pub fn cursor_goto(&mut self, row: usize, column: usize) {
self.write_cap_with_params(
"cup",
&[Param::Number(row as i32), Param::Number(column as i32)],
);
}
pub fn cursor_up(&mut self, amount: usize) {
match amount {
0 => {}
1 => self.write_cap("cuu1"),
_ => self.write_cap_with_params("cuu", &[Param::Number(amount as i32)]),
}
}
pub fn cursor_down(&mut self, amount: usize) {
match amount {
0 => {}
1 => self.write_cap("cud1"),
_ => self.write_cap_with_params("cud", &[Param::Number(amount as i32)]),
}
}
pub fn cursor_forward(&mut self, amount: usize) {
match amount {
0 => {}
1 => self.write_cap("cuf1"),
_ => self.write_cap_with_params("cuf", &[Param::Number(amount as i32)]),
}
}
pub fn cursor_backward(&mut self, amount: usize) {
match amount {
0 => {}
1 => self.write_cap("cub1"),
_ => self.write_cap_with_params("cub", &[Param::Number(amount as i32)]),
}
}
pub fn hide_cursor(&mut self) {
self.write_cap("civis");
}
pub fn show_cursor(&mut self) {
self.write_cap("cnorm");
}
pub fn ask_for_cpr(&mut self) {
self.write_raw("\x1b[6n".as_bytes());
self.flush()
}
pub fn bell(&mut self) {
self.write_cap("bel");
self.flush()
}
pub fn terminal_size(&self) -> io::Result<(usize, usize)> {
terminal_size(self.stdout.as_raw_fd())
}
pub fn enable_bracketed_paste(&mut self) {
self.write_raw("\x1b[?2004h".as_bytes());
}
pub fn disable_bracketed_paste(&mut self) {
self.write_raw("\x1b[?2004l".as_bytes());
}
pub fn execute(&mut self, cmd: Command) {
match cmd {
Command::PutChar(c) => self.write(c.to_string().as_str()),
Command::Write(content) => self.write(&content),
Command::SetTitle(title) => self.set_title(&title),
Command::ClearTitle => self.clear_title(),
Command::Flush => self.flush(),
Command::EraseScreen => self.erase_screen(),
Command::AlternateScreen(enable) => {
if enable {
self.enter_alternate_screen()
} else {
self.quit_alternate_screen()
}
}
Command::MouseSupport(enable) => {
if enable {
self.enable_mouse_support();
} else {
self.disable_mouse_support();
}
}
Command::EraseEndOfLine => self.erase_end_of_line(),
Command::EraseDown => self.erase_down(),
Command::ResetAttributes => self.reset_attributes(),
Command::Fg(fg) => self.set_fg(fg),
Command::Bg(bg) => self.set_bg(bg),
Command::Effect(effect) => self.set_effect(effect),
Command::SetAttribute(attr) => self.set_attribute(attr),
Command::AutoWrap(enable) => {
if enable {
self.enable_autowrap();
} else {
self.disable_autowrap();
}
}
Command::CursorGoto { row, col } => self.cursor_goto(row, col),
Command::CursorUp(amount) => self.cursor_up(amount),
Command::CursorDown(amount) => self.cursor_down(amount),
Command::CursorLeft(amount) => self.cursor_backward(amount),
Command::CursorRight(amount) => self.cursor_forward(amount),
Command::CursorShow(show) => {
if show {
self.show_cursor()
} else {
self.hide_cursor()
}
}
Command::BracketedPaste(enable) => {
if enable {
self.enable_bracketed_paste()
} else {
self.disable_bracketed_paste()
}
}
}
}
}
#[derive(Debug, Clone)]
pub enum Command {
PutChar(char),
Write(String),
SetTitle(String),
ClearTitle,
Flush,
EraseScreen,
AlternateScreen(bool),
MouseSupport(bool),
EraseEndOfLine,
EraseDown,
ResetAttributes,
Fg(Color),
Bg(Color),
Effect(Effect),
SetAttribute(Attr),
AutoWrap(bool),
CursorGoto { row: usize, col: usize },
CursorUp(usize),
CursorDown(usize),
CursorLeft(usize),
CursorRight(usize),
CursorShow(bool),
BracketedPaste(bool),
}