use std::io::{Stdout, Write, BufWriter, BufRead};
use std::sync::{Arc, Mutex, RwLock};
use termion;
use termion::screen::AlternateScreen;
use termion::raw::{IntoRawMode, RawTerminal};
use parse_ansi::parse_bytes;
use crate::fail::{HResult, ErrorLog};
pub type TermMode = AlternateScreen<RawTerminal<BufWriter<Stdout>>>;
#[derive(Clone)]
pub struct Screen {
screen: Arc<Mutex<TermMode>>,
size: Arc<RwLock<Option<(usize, usize)>>>,
terminal: String
}
impl Screen {
pub fn new() -> HResult<Screen> {
let screen = BufWriter::new(std::io::stdout()).into_raw_mode()?;
let mut screen = AlternateScreen::from(screen);
let terminal = std::env::var("TERM").unwrap_or("xterm".into());
screen.cursor_hide()?;
Ok(Screen {
screen: Arc::new(Mutex::new(screen)),
size: Arc::new(RwLock::new(None)),
terminal: terminal
})
}
pub fn set_size(&self, size: (usize, usize)) -> HResult<()> {
*self.size.write()? = Some(size);
Ok(())
}
pub fn is_resized(&self) -> HResult<bool> {
Ok(self.size.read()?.is_some())
}
pub fn get_size(&self) -> HResult<(usize, usize)> {
match self.size.read()?.clone() {
Some((xsize, ysize)) => Ok((xsize, ysize)),
None => Ok((self.xsize()?, self.ysize()?))
}
}
pub fn take_size(&self) -> HResult<(usize, usize)> {
Ok(self.size.write()?.take()?)
}
pub fn set_title(&mut self, title: &str) -> HResult<()> {
if self.terminal.starts_with("xterm") ||
self.terminal.starts_with("screen") ||
self.terminal.starts_with("tmux"){
write!(self, "\x1b]2;hunter: {}\x1b\\", title)?;
}
if self.terminal.starts_with("tmux") {
write!(self, "\x1bkhunter: {}\x1b\\", title)?;
}
Ok(())
}
}
impl Write for Screen {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.screen
.lock()
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other,
"Screen Mutex poisoned!"))
.and_then(|mut s| s.write(buf))
}
fn flush(&mut self) -> std::io::Result<()> {
self.screen
.lock()
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other,
"Screen Mutex poisoned!"))
.and_then(|mut s| s.flush())
}
}
pub trait ScreenExt: Write {
fn suspend_raw_mode(&mut self) -> HResult<()>;
fn activate_raw_mode(&mut self) -> HResult<()>;
fn suspend(&mut self) -> HResult<()> {
self.cursor_show().log();
self.suspend_raw_mode().log();
self.to_main_screen()
}
fn activate(&mut self) -> HResult<()> {
self.cursor_hide().log();
self.activate_raw_mode().log();
self.to_alternate_screen()
}
fn cursor_hide(&mut self) -> HResult<()> {
write!(self, "{}", termion::cursor::Hide)?;
self.flush()?;
Ok(())
}
fn cursor_show(&mut self) -> HResult<()> {
write!(self, "{}", termion::cursor::Show)?;
self.flush()?;
Ok(())
}
fn reset(&mut self) -> HResult<()> {
write!(self, "{}", termion::style::Reset)?;
Ok(())
}
fn clear(&mut self) -> HResult<()> {
write!(self, "{}{}",
termion::style::Reset,
termion::clear::All)?;
Ok(())
}
fn write_str(&mut self, str: &str) -> HResult<()> {
write!(self, "{}", str)?;
Ok(())
}
fn goto_xy(&mut self, x: usize, y: usize) -> HResult<()> {
let x = x as u16;
let y = y as u16;
write!(self, "{}", goto_xy(x + 1, y + 1))?;
Ok(())
}
fn size(&self) -> HResult<(usize, usize)> {
let (xsize, ysize) = termion::terminal_size()?;
Ok(((xsize-1) as usize, (ysize-1) as usize))
}
fn xsize(&self) -> HResult<usize> {
let (xsize, _) = termion::terminal_size()?;
Ok((xsize - 1) as usize)
}
fn ysize(&self) -> HResult<usize> {
let (_, ysize) = termion::terminal_size()?;
Ok((ysize - 1) as usize)
}
fn to_main_screen(&mut self) -> HResult<()> {
write!(self, "{}", termion::screen::ToMainScreen)?;
self.flush()?;
Ok(())
}
fn to_alternate_screen(&mut self) -> HResult<()> {
write!(self, "{}", termion::screen::ToAlternateScreen)?;
self.flush()?;
Ok(())
}
}
impl ScreenExt for Screen {
fn suspend_raw_mode(&mut self) -> HResult<()> {
self.screen
.lock()?
.suspend_raw_mode()
}
fn activate_raw_mode(&mut self) -> HResult<()> {
self.screen
.lock()?
.activate_raw_mode()
}
}
impl ScreenExt for TermMode {
fn suspend_raw_mode(&mut self) -> HResult<()> {
Ok(RawTerminal::suspend_raw_mode(self)?)
}
fn activate_raw_mode(&mut self) -> HResult<()> {
Ok(RawTerminal::activate_raw_mode(self)?)
}
}
pub fn flush_stdin() {
let stdin = std::io::stdin();
let mut stdin = stdin.lock();
stdin.consume(10);
}
pub fn xsize() -> u16 {
let (xsize, _) = termion::terminal_size().unwrap();
xsize
}
pub fn xsize_u() -> usize {
let (xsize, _) = termion::terminal_size().unwrap();
xsize as usize - 1
}
pub fn ysize() -> u16 {
let (_, ysize) = termion::terminal_size().unwrap();
ysize
}
pub fn size() -> HResult<(usize, usize)> {
let (xsize, ysize) = termion::terminal_size()?;
Ok(((xsize-1) as usize, (ysize-1) as usize))
}
pub fn sized_string(string: &str, xsize: u16) -> String {
string.chars().fold("".to_string(), |acc, ch| {
let width: usize = unicode_width::UnicodeWidthStr::width_cjk(acc.as_str());
if width + 1 >= xsize as usize {
acc
} else {
acc + &ch.to_string()
}
})
}
fn is_ansi(ansi_pos: &Vec<(usize, usize)>, char_pos: &usize) -> bool {
ansi_pos.iter().fold(false, |is_ansi, (start, end)| {
if char_pos >= start && char_pos <= end {
true
} else { is_ansi }
})
}
fn ansi_len_at(ansi_pos: &Vec<(usize, usize)>, char_pos: &usize) -> usize {
ansi_pos.iter().fold(0, |len, (start, end)| {
if char_pos >= start && char_pos <= end {
len + (char_pos - start)
} else if char_pos >= end {
len + (end - start)
} else {
len
}
})
}
pub fn sized_string_u(string: &str, xsize: usize) -> String {
let ansi_pos = parse_bytes(string.as_bytes()).map(|m| {
(m.start(), m.end())
}).collect();
let sized = string.chars().fold(String::new(), |acc, ch| {
let width: usize = unicode_width::UnicodeWidthStr::width_cjk(acc.as_str());
let ansi_len = ansi_len_at(&ansi_pos, &acc.len());
let unprinted = acc.len() - width;
if width + unprinted >= xsize + ansi_len + 1{
acc
} else {
acc + &ch.to_string()
}
});
let ansi_len = ansi_len_at(&ansi_pos, &sized.len());
let padded = format!("{:padding$}", sized, padding=xsize + ansi_len + 1);
padded
}
pub fn highlight_color() -> String {
format!(
"{}",
termion::color::Fg(termion::color::LightGreen),
)
}
pub fn normal_color() -> String {
format!(
"{}",
termion::color::Fg(termion::color::White),
)
}
pub fn color_red() -> String {
format!("{}", termion::color::Fg(termion::color::Red))
}
pub fn color_yellow() -> String {
format!("{}", termion::color::Fg(termion::color::Yellow))
}
pub fn color_green() -> String {
format!("{}", termion::color::Fg(termion::color::Green))
}
pub fn color_light_green() -> String {
format!("{}", termion::color::Fg(termion::color::LightGreen))
}
pub fn color_cyan() -> String {
format!("{}", termion::color::Fg(termion::color::Cyan))
}
pub fn color_light_yellow() -> String {
format!("{}", termion::color::Fg(termion::color::LightYellow))
}
pub fn color_orange() -> String {
let color = termion::color::Fg(termion::color::AnsiValue::rgb(5 as u8 ,
4 as u8,
0 as u8));
format!("{}", color)
}
pub fn from_lscolor(color: &lscolors::Color) -> String {
match color {
lscolors::Color::Black
=> format!("{}", termion::color::Fg(termion::color::Black)),
lscolors::Color::Red
=> format!("{}", termion::color::Fg(termion::color::Red)),
lscolors::Color::Green
=> format!("{}", termion::color::Fg(termion::color::Green)),
lscolors::Color::Yellow
=> format!("{}", termion::color::Fg(termion::color::Yellow)),
lscolors::Color::Blue
=> format!("{}", termion::color::Fg(termion::color::Blue)),
lscolors::Color::Magenta
=> format!("{}", termion::color::Fg(termion::color::Magenta)),
lscolors::Color::Cyan
=> format!("{}", termion::color::Fg(termion::color::Cyan)),
lscolors::Color::White
=> format!("{}", termion::color::Fg(termion::color::White)),
_ => format!("{}", normal_color()),
}
}
pub fn gotoy(y: u16) -> String {
format!("{}", termion::cursor::Goto(1, y))
}
pub fn goto_xy(x: u16, y: u16) -> String {
format!("{}", termion::cursor::Goto(x, y))
}
pub fn goto_xy_u(x: usize, y: usize) -> String {
let x = (x+1) as u16;
let y = (y+1) as u16;
format!("{}", termion::cursor::Goto(x, y))
}
pub fn move_bottom() -> String {
gotoy(ysize())
}
pub fn reset() -> String {
format!("{}", termion::style::Reset)
}
pub fn invert() -> String {
format!("{}", termion::style::Invert)
}
pub fn cursor_save() -> String {
format!("{}", termion::cursor::Save)
}
pub fn cursor_restore() -> String {
format!("{}", termion::cursor::Restore)
}
pub fn header_color() -> String {
format!(
"{}{}",
termion::color::Fg(termion::color::White),
termion::color::Bg(termion::color::Blue)
)
}
pub fn status_bg() -> String {
format!("{}", termion::color::Bg(termion::color::LightBlue))
}