hunter/src/term.rs

469 lines
13 KiB
Rust

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::unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
use crate::fail::{HResult, ErrorLog};
use crate::trait_ext::ExtractResult;
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();
// Not 100% sure if it's OK to just call consume like this
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 size_pixels() -> HResult<(usize, usize)> {
let (xsize, ysize) = termion::terminal_size_pixels()?;
Ok((xsize as usize, ysize as usize))
}
pub fn cell_ratio() -> HResult<f32> {
let (xsize, ysize) = size()?;
let (xpix, ypix) = size_pixels()?;
let cell_xpix = xpix as f32 / (xsize+1) as f32;
let cell_ypix = ypix as f32 / (ysize+1) as f32;
let ratio = cell_xpix / cell_ypix;
Ok(ratio)
}
pub fn sized_string(string: &str, xsize: u16) -> String {
string.chars().fold("".to_string(), |acc, ch| {
let width: usize = unicode_width::UnicodeWidthStr::width(acc.as_str());
if width + 1 >= xsize as usize {
acc
} else {
acc + &ch.to_string()
}
})
}
#[derive(Debug)]
enum Token<'a> {
Text(&'a str),
Ansi(&'a str)
}
fn get_tokens(string: &str) -> Vec<Token> {
let mut tokens = parse_bytes(string.as_bytes())
.fold((Vec::new(), 0), |(mut tokens, last_tok), ansi_pos| {
if last_tok == 0 {
// first iteration
if ansi_pos.start() != 0 {
// there is text before first ansi code
tokens.push(Token::Text(&string[0..ansi_pos.start()]));
}
tokens.push(Token::Ansi(&string[ansi_pos.start()..ansi_pos.end()]));
(tokens, ansi_pos.end())
} else if last_tok == ansi_pos.start() {
// next token is another ansi code
tokens.push(Token::Ansi(&string[ansi_pos.start()..ansi_pos.end()]));
(tokens, ansi_pos.end())
} else {
// there is text before the next ansi code
tokens.push(Token::Text(&string[last_tok..ansi_pos.start()]));
tokens.push(Token::Ansi(&string[ansi_pos.start()..ansi_pos.end()]));
(tokens, ansi_pos.end())
}
});
// last part is just text, add it to tokens
if string.len() > tokens.1 {
tokens.0.push(Token::Text(&string[tokens.1..string.len()]));
}
tokens.0
}
pub fn string_len(string: &str) -> usize {
let tokens = get_tokens(&string);
tokens.iter().fold(0, |len, token| {
match token {
Token::Text(text) => len + text.len(),
_ => len
}
})
}
pub fn sized_string_u(string: &str, xsize: usize) -> String {
let tokens = get_tokens(&string);
let sized = tokens.iter().try_fold((String::new(), 0), |(mut sized, width), token| {
let (tok, tok_width) = match token {
Token::Text(text) => {
let tok_str = text;
let tok_width = text.width();
(tok_str, tok_width)
},
Token::Ansi(ansi) => (ansi, 0)
};
// adding this token makes string larger than xsise
if width + tok_width > xsize {
let chars_left = xsize + 1 - width;
// fill up with chars from token until xsize is reached
let fillup = tok.chars().try_fold((String::new(), 0),
|(mut fillup, fillup_width), chr| {
let chr_width = chr.width().unwrap_or(0);
if fillup_width + chr_width > chars_left {
Err((fillup, fillup_width))
} else {
fillup.push(chr);
Ok((fillup, fillup_width + chr_width))
}
});
let (fillup, fillup_width) = fillup.extract();
sized.push_str(&fillup);
// we're done here, stop looping
Err((sized, width + fillup_width))
} else {
sized.push_str(&tok);
Ok((sized, width + tok_width))
}
});
let (mut sized_str, sized_width) = sized.extract();
// pad out string
if sized_width < xsize {
let padding = xsize-sized_width;
for _ in 0..padding {
sized_str += " ";
}
}
sized_str
}
// Do these as constants
pub fn highlight_color() -> String {
format!(
"{}",
termion::color::Fg(termion::color::LightGreen),
//termion::color::Bg(termion::color::Black)
)
}
pub fn normal_color() -> String {
format!(
"{}",
termion::color::Fg(termion::color::White),
//termion::color::Bg(termion::color::Black)
)
}
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 cursor_left(n: u16) -> String {
// format!("{}", termion::cursor::Left(n))
// }
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_top() -> String {
// gotoy(1)
// }
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))
}