use std::fmt::Display;
use std::io;
use std::io::{
Write,
Result as IoResult,
ErrorKind,
Read
};
use std::os::fd::{
AsFd,
AsRawFd,
RawFd
};
use std::ops::{
Deref,
DerefMut
};
use termios::{
ECHO,
ICANON,
TCSANOW,
Termios,
tcsetattr,
};
#[macro_use]
pub(crate) mod escapes {
#![allow(dead_code)]
pub const RESET: &str = "\x1B[0m";
pub const BOLD: &str = "\x1B[1m";
pub const DIMMED: &str ="\x1B[2m";
pub const ITALIC: &str ="\x1B[3m";
pub const UNDERLINE: &str ="\x1B[4m";
macro_rules! fg_normal {
($color:literal) => {
concat!("\x1B[", "3", $color, "m")
};
}
macro_rules! fg_intense {
($color:literal) => {
concat!("\x1B[", "3", "8;5;", $color, "m")
};
}
}
pub const COLOR_CODES: [ColorCode; 16] = [
ColorCode::White,
ColorCode::IntenseWhite,
ColorCode::Yellow,
ColorCode::IntenseYellow,
ColorCode::Red,
ColorCode::IntenseRed,
ColorCode::Magenta,
ColorCode::IntenseMagenta,
ColorCode::Blue,
ColorCode::IntenseBlue,
ColorCode::Cyan,
ColorCode::IntenseCyan,
ColorCode::Green,
ColorCode::IntenseGreen,
ColorCode::Black,
ColorCode::IntenseBlack
];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum ColorCode {
Black,
Blue,
Green,
Red,
Cyan,
Magenta,
Yellow,
White,
IntenseBlack,
IntenseBlue,
IntenseGreen,
IntenseRed,
IntenseCyan,
IntenseMagenta,
IntenseYellow,
IntenseWhite
}
pub(crate) struct NonBlockingStdin<'l> {
lock: io::StdinLock<'l>
}
pub(crate) struct EchoDisabler {
fd: RawFd
}
pub(crate) struct InputRequest<T> {
pub sender: T,
pub buffer: Vec<u8>,
}
pub trait EmitEscapes: Write + Sized {
fn escapes_recognized(&self) -> bool;
fn set_color(&mut self, color: ColorCode) -> IoResult<()> {
if self.escapes_recognized() {
color.write(self)
}
else {Ok(())}
}
fn set_bold(&mut self) -> IoResult<()> {
if self.escapes_recognized() {
self.write_all(escapes::BOLD.as_bytes())
}
else {Ok(())}
}
fn reset(&mut self) -> IoResult<()> {
if self.escapes_recognized() {
self.write_all(escapes::RESET.as_bytes())
}
else {Ok(())}
}
fn write_with_color<T: Display + ?Sized>(
&mut self,
color: ColorCode,
thing: &T)
{
self.set_color(color).unwrap();
write!(self, "{thing}").unwrap();
self.reset().unwrap();
}
fn write_with_color_bold<T: Display + ?Sized>(
&mut self,
color: ColorCode,
thing: &T)
{
self.set_bold().unwrap();
self.set_color(color).unwrap();
write!(self, "{thing}").unwrap();
self.reset().unwrap();
}
}
impl ColorCode {
fn write<W: Write>(self, writer: &mut W) -> IoResult<()> {
use ColorCode::*;
let escape = match self {
Black => fg_normal!("0"),
Blue => fg_normal!("4"),
Green => fg_normal!("2"),
Red => fg_normal!("1"),
Cyan => fg_normal!("6"),
Magenta => fg_normal!("5"),
Yellow => fg_normal!("3"),
White => fg_normal!("7"),
IntenseBlack => fg_intense!("8"),
IntenseBlue => fg_intense!("12"),
IntenseGreen => fg_intense!("10"),
IntenseRed => fg_intense!("9"),
IntenseCyan => fg_intense!("14"),
IntenseMagenta => fg_intense!("13"),
IntenseYellow => fg_intense!("11"),
IntenseWhite => fg_intense!("15")
};
writer.write_all(escape.as_bytes())
}
pub(crate) const fn from_str(s: &str) -> Option<Self> {
match s.as_bytes() {
b"0" | b"Black" => Some(Self::Black),
b"4" | b"Blue" => Some(Self::Blue),
b"2" | b"Green" => Some(Self::Green),
b"1" | b"Red" => Some(Self::Red),
b"6" | b"Cyan" => Some(Self::Cyan),
b"5" | b"Magenta" => Some(Self::Magenta),
b"3" | b"Yellow" => Some(Self::Yellow),
b"7" | b"White" => Some(Self::White),
b"8" | b"IntenseBlack" => Some(Self::IntenseBlack),
b"12" | b"IntenseBlue" => Some(Self::IntenseBlue),
b"10" | b"IntenseGreen" => Some(Self::IntenseGreen),
b"9" | b"IntenseRed" => Some(Self::IntenseRed),
b"14" | b"IntenseCyan" => Some(Self::IntenseCyan),
b"13" | b"IntenseMagenta" => Some(Self::IntenseMagenta),
b"11" | b"IntenseYellow" => Some(Self::IntenseYellow),
b"15" | b"IntenseWhite" => Some(Self::IntenseWhite),
_ => None
}
}
pub(crate) const fn as_number(self) -> u8 {
match self {
Self::Black => 0,
Self::Blue => 4,
Self::Green => 2,
Self::Red => 1,
Self::Cyan => 6,
Self::Magenta => 5,
Self::Yellow => 3,
Self::White => 7,
Self::IntenseBlack => 8,
Self::IntenseBlue => 12,
Self::IntenseGreen => 10,
Self::IntenseRed => 9,
Self::IntenseCyan => 14,
Self::IntenseMagenta => 13,
Self::IntenseYellow => 11,
Self::IntenseWhite => 15
}
}
}
impl<E: EmitEscapes> EmitEscapes for &mut E {
fn escapes_recognized(&self) -> bool {
(**self).escapes_recognized()
}
}
impl NonBlockingStdin<'static> {
pub fn unblock() -> io::Result<Self> {
let lock = io::stdin().lock();
set_nonblocking(&lock)?;
Ok(Self {lock})
}
pub(crate) fn drain_input(&mut self, buffer: &mut Vec<u8>)
-> io::Result<Option<String>>
{
const BACKSPACE: u8 = b'\x7F';
const ESCAPE: u8 = b'\x1B';
let mut byte = [0u8; 1];
loop {
match (self.read(&mut byte), byte[0]) {
(Ok(0), _) |
(Ok(1), b'\n' | b'\r' | 0x03 | 0x04) => {
let buffer = std::mem::take(buffer);
return match String::from_utf8(buffer) {
Ok(s) => Ok(Some(s)),
Err(e) => {
let cow = String::from_utf8_lossy(e.as_bytes());
Ok(Some(cow.into_owned()))
}
}
},
(Ok(1), BACKSPACE) => {
if let Some((idx, _c)) = String::from_utf8_lossy(buffer)
.char_indices()
.next_back()
{
buffer.truncate(idx);
}
},
(Ok(1), ESCAPE) => buffer.push(b'^'),
(Ok(1), b) => buffer.push(b),
(Ok(_n), _) => unreachable!(),
(Err(e), _) if e.kind() == ErrorKind::Interrupted => continue,
(Err(e), _) if e.kind() == ErrorKind::WouldBlock => break,
(Err(err), _) => return Err(err)
}
}
Ok(None)
}
}
impl<'l> Deref for NonBlockingStdin<'l> {
type Target = io::StdinLock<'l>;
fn deref(&self) -> &Self::Target {&self.lock}
}
impl<'l> DerefMut for NonBlockingStdin<'l> {
fn deref_mut(&mut self) -> &mut Self::Target {&mut self.lock}
}
impl<'l> Drop for NonBlockingStdin<'l> {
fn drop(&mut self) {
set_blocking(&self.lock)
.expect("resetting stdin to blocking IO should succeed");
}
}
fn set_nonblocking<T: AsFd>(fd: &T) -> io::Result<()> {
let previous = unsafe {
libc::fcntl(fd.as_fd().as_raw_fd(), libc::F_GETFL)
};
if previous == -1 {
return Err(io::Error::last_os_error());
}
let flags = previous | libc::O_NONBLOCK;
if flags != previous {
let ret = unsafe {
libc::fcntl(fd.as_fd().as_raw_fd(), libc::F_SETFL, flags)
};
if ret < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
fn set_blocking<T: AsFd>(fd: &T) -> io::Result<()> {
let previous = unsafe {
libc::fcntl(fd.as_fd().as_raw_fd(), libc::F_GETFL)
};
if previous == -1 {
return Err(io::Error::last_os_error());
}
let flags = previous & !libc::O_NONBLOCK;
if flags != previous {
let r = unsafe {
libc::fcntl(fd.as_fd().as_raw_fd(), libc::F_SETFL, flags)
};
if r == -1 {
return Err(io::Error::last_os_error())
}
}
Ok(())
}
impl EchoDisabler {
#[must_use = "Echo is disabled for the lifetime of the struct"]
pub fn on_stdin() -> Self {
let fd = std::io::stdin().as_raw_fd();
Self::disable_with_fd(fd)
}
#[must_use = "Echo is disabled for the lifetime of the struct"]
pub fn on_stderr() -> Self {
let fd = std::io::stderr().as_raw_fd();
Self::disable_with_fd(fd)
}
fn disable_with_fd(fd: RawFd) -> Self {
let mut termios = Termios::from_fd(fd).unwrap();
termios.c_lflag &= !ECHO;
tcsetattr(fd, TCSANOW, &termios).unwrap();
Self {fd}
}
pub fn reset(self) {}
pub fn reset_stdin() {
let fd = std::io::stdin().as_raw_fd();
Self::reset_with_fd(fd)
}
pub fn reset_stderr() {
let fd = std::io::stderr().as_raw_fd();
Self::reset_with_fd(fd)
}
fn reset_with_fd(fd: RawFd) {
let mut termios = Termios::from_fd(fd).unwrap();
termios.c_lflag |= ECHO;
tcsetattr(fd, TCSANOW, &termios).unwrap();
}
}
impl Drop for EchoDisabler {
fn drop(&mut self) {
Self::reset_with_fd(self.fd)
}
}
#[test]
fn set_blocking_nonblocking() -> io::Result<()> {
let mut stdin = io::stdin();
set_nonblocking(&stdin)?;
assert_eq!(
stdin.read(&mut [0; 1]).map_err(|err| err.kind()),
Err(io::ErrorKind::WouldBlock)
);
set_blocking(&stdin)?;
Ok(())
}
impl<T> InputRequest<T> {
pub fn replace(stored: &mut Option<Self>, new_sender: T) {
if let Some(old) = stored.as_mut() {
old.sender = new_sender;
old.buffer.clear();
}
else {
let stdin_fd = io::stdin().as_raw_fd();
let mut termios = Termios::from_fd(stdin_fd).unwrap();
termios.c_lflag &= !ICANON;
tcsetattr(stdin_fd, TCSANOW, &termios).unwrap();
*stored = Some(Self { sender: new_sender, buffer: Vec::new() });
}
}
}
impl<T> Drop for InputRequest<T> {
fn drop(&mut self) {
let stdin_fd = io::stdin().as_raw_fd();
let mut termios = Termios::from_fd(stdin_fd).unwrap();
termios.c_lflag |= ICANON;
tcsetattr(stdin_fd, TCSANOW, &termios).unwrap();
}
}
#[test]
fn block_unblock() -> io::Result<()> {
let nonblocking = NonBlockingStdin::unblock()?;
drop(nonblocking);
Ok(())
}