use async_trait::async_trait;
use endbasic_core::exec::Clearable;
use endbasic_core::syms::Symbols;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::env;
use std::io;
use std::rc::Rc;
use std::str;
mod cmds;
pub(crate) use cmds::add_all;
mod colors;
pub use colors::{ansi_color_to_rgb, RGB};
mod format;
pub(crate) use format::refill_and_print;
mod readline;
pub use readline::read_line;
pub(crate) use readline::read_line_secure;
mod trivial;
pub use trivial::TrivialConsole;
#[derive(Clone, Debug, PartialEq)]
pub enum Key {
ArrowDown,
ArrowLeft,
ArrowRight,
ArrowUp,
Backspace,
CarriageReturn,
Char(char),
End,
Eof,
Escape,
Interrupt,
Home,
NewLine,
PageDown,
PageUp,
Tab,
Unknown(String),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClearType {
All,
CurrentLine,
PreviousChar,
UntilNewLine,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct CharsXY {
pub x: u16,
pub y: u16,
}
impl CharsXY {
pub fn new(x: u16, y: u16) -> Self {
Self { x, y }
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PixelsXY {
pub x: i16,
pub y: i16,
}
impl PixelsXY {
pub fn new(x: i16, y: i16) -> Self {
Self { x, y }
}
}
#[async_trait(?Send)]
pub trait Console {
fn clear(&mut self, how: ClearType) -> io::Result<()>;
fn color(&mut self, fg: Option<u8>, bg: Option<u8>) -> io::Result<()>;
fn enter_alt(&mut self) -> io::Result<()>;
fn hide_cursor(&mut self) -> io::Result<()>;
fn is_interactive(&self) -> bool;
fn leave_alt(&mut self) -> io::Result<()>;
fn locate(&mut self, pos: CharsXY) -> io::Result<()>;
fn move_within_line(&mut self, off: i16) -> io::Result<()>;
fn print(&mut self, text: &str) -> io::Result<()>;
async fn poll_key(&mut self) -> io::Result<Option<Key>>;
async fn read_key(&mut self) -> io::Result<Key>;
fn show_cursor(&mut self) -> io::Result<()>;
fn size(&self) -> io::Result<CharsXY>;
fn write(&mut self, bytes: &[u8]) -> io::Result<()>;
fn draw_line(&mut self, _x1y1: PixelsXY, _x2y2: PixelsXY) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "No graphics support in this console"))
}
fn draw_pixel(&mut self, _xy: PixelsXY) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "No graphics support in this console"))
}
fn draw_rect(&mut self, _x1y1: PixelsXY, _x2y2: PixelsXY) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "No graphics support in this console"))
}
fn draw_rect_filled(&mut self, _x1y1: PixelsXY, _x2y2: PixelsXY) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "No graphics support in this console"))
}
fn sync_now(&mut self) -> io::Result<()>;
fn set_sync(&mut self, _enabled: bool) -> io::Result<()>;
}
pub(crate) struct ConsoleClearable {
console: Rc<RefCell<dyn Console>>,
}
impl ConsoleClearable {
pub(crate) fn new(console: Rc<RefCell<dyn Console>>) -> Box<Self> {
Box::from(Self { console })
}
}
impl Clearable for ConsoleClearable {
fn reset_state(&self, _syms: &mut Symbols) {
let mut console = self.console.borrow_mut();
let _ = console.leave_alt();
let _ = console.color(None, None);
let _ = console.show_cursor();
let _ = console.set_sync(true);
}
}
pub fn has_control_chars_str(s: &str) -> bool {
for ch in s.chars() {
if ch.is_control() {
return true;
}
}
false
}
pub fn has_control_chars_u8(s: &[u8]) -> bool {
for ch in s {
if *ch < 32 {
return true;
}
}
false
}
pub fn get_env_var_as_u16(name: &str) -> Option<u16> {
match env::var_os(name) {
Some(value) => value.as_os_str().to_string_lossy().parse::<u16>().map(Some).unwrap_or(None),
None => None,
}
}
fn line_to_keys(s: String) -> VecDeque<Key> {
let mut keys = VecDeque::default();
for ch in s.chars() {
if ch == '\x1b' {
keys.push_back(Key::Escape);
} else if ch == '\n' {
keys.push_back(Key::NewLine);
} else if ch == '\r' {
} else if !ch.is_control() {
keys.push_back(Key::Char(ch));
} else {
keys.push_back(Key::Unknown(format!("{}", ch)));
}
}
keys
}
pub fn read_key_from_stdin(buffer: &mut VecDeque<Key>) -> io::Result<Key> {
if buffer.is_empty() {
let mut line = String::new();
if io::stdin().read_line(&mut line)? == 0 {
return Ok(Key::Eof);
}
*buffer = line_to_keys(line);
}
match buffer.pop_front() {
Some(key) => Ok(key),
None => Ok(Key::Eof),
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_has_control_chars_str() {
use crate::console::has_control_chars_str;
assert!(!has_control_chars_str(""));
assert!(!has_control_chars_str("foo bar^baz"));
assert!(has_control_chars_str("foo\nbar"));
assert!(has_control_chars_str("foo\rbar"));
assert!(has_control_chars_str("foo\x08bar"));
}
#[test]
fn test_has_control_chars_u8() {
use crate::console::has_control_chars_u8;
assert!(!has_control_chars_u8(b""));
assert!(!has_control_chars_u8(b"foo bar^baz"));
assert!(has_control_chars_u8(b"foo\nbar"));
assert!(has_control_chars_u8(b"foo\rbar"));
assert!(has_control_chars_u8(b"foo\x08bar"));
}
}