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, AnsiColor, RGB};
mod format;
pub use format::refill_and_print;
mod readline;
pub use readline::{read_line, read_line_secure};
mod trivial;
pub use trivial::TrivialConsole;
mod linebuffer;
pub use linebuffer::LineBuffer;
#[derive(Clone, Debug, Eq, 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 }
}
}
#[derive(Clone, Copy, Debug)]
pub struct SizeInPixels {
pub width: u16,
pub height: u16,
}
#[async_trait(?Send)]
pub trait Console {
fn clear(&mut self, how: ClearType) -> io::Result<()>;
fn color(&self) -> (Option<u8>, Option<u8>);
fn set_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_chars(&self) -> io::Result<CharsXY>;
fn size_pixels(&self) -> io::Result<SizeInPixels> {
Err(io::Error::new(io::ErrorKind::Other, "No graphics support in this console"))
}
fn write(&mut self, text: &str) -> io::Result<()>;
fn draw_circle(&mut self, _center: PixelsXY, _radius: u16) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "No graphics support in this console"))
}
fn draw_circle_filled(&mut self, _center: PixelsXY, _radius: u16) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "No graphics support in this console"))
}
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<bool>;
}
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.set_color(None, None);
let _ = console.show_cursor();
let _ = console.set_sync(true);
}
}
pub fn has_control_chars(s: &str) -> bool {
for ch in s.chars() {
if ch.is_control() {
return true;
}
}
false
}
pub fn remove_control_chars<S: Into<String>>(s: S) -> String {
let s = s.into();
if !has_control_chars(&s) {
return s;
}
let mut o = String::with_capacity(s.len());
for ch in s.chars() {
if ch.is_control() {
o.push(' ');
} else {
o.push(ch);
}
}
o
}
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 {
use super::*;
#[test]
fn test_has_control_chars() {
assert!(!has_control_chars(""));
assert!(!has_control_chars("foo bar^baz"));
assert!(has_control_chars("foo\nbar"));
assert!(has_control_chars("foo\rbar"));
assert!(has_control_chars("foo\x08bar"));
}
#[test]
fn test_remove_control_chars() {
assert_eq!("", remove_control_chars(""));
assert_eq!("foo bar", remove_control_chars("foo bar"));
assert_eq!("foo bar baz ", remove_control_chars("foo\r\nbar\rbaz\n"));
}
}