#![allow(unused)] use std::{io::Read, mem::MaybeUninit}; use crate::reader::esc_parse::{InputChar, bell, hide_cursor, show_cursor}; pub mod esc_parse; #[derive(Default)] pub struct ReaderState { prompt: String, line: String, cursor: usize, max_len: usize, history: Vec, history_inx: Option, history_saved: Option, } #[derive(Debug)] pub enum InitTTYError { NotATTY } #[derive(Debug)] pub enum ReadError { EscapeError(esc_parse::AcceptError), Exited, } #[derive(Debug)] pub enum EditCommand { NewLine, HistoryPrevious, HistoryNext, CursorBack, CursorForward, DeleteBack, Exit, } macro_rules! edit_cmd { ($id:ident) => { Result::::Ok(EditCommand::$id) }; ($id:ident, $cmd:expr) => { Ok(EditCommand::$id($cmd)) }; } impl ReaderState { pub fn new(prompt: &str) -> Self { ReaderState { prompt: String::from(prompt), max_len: prompt.len(), ..Default::default() } } pub fn print(&mut self, txt: String) { eprint!("{}", txt); self.max_len += txt.len(); } fn clear(&mut self) { esc_parse::clear_line(); self.max_len = self.prompt.len(); } pub fn empty(&mut self) { self.set_line(String::from("")); } pub fn init_tty() -> Result<(), InitTTYError> { unsafe { use libc::{isatty, termios, ECHO, ICANON, INPCK, TCSANOW, tcgetattr, tcsetattr}; if !(isatty(0) == 1) { return Result::Err(InitTTYError::NotATTY); } let mut termios: termios = MaybeUninit::zeroed().assume_init(); tcgetattr(0, &mut termios); termios.c_lflag &= !(ECHO | ICANON); termios.c_iflag &= !(INPCK); tcsetattr(0, TCSANOW, &termios); return Ok(()); } } pub fn insert_at_cursor(&mut self, txt: String) { self.line.insert_str(self.cursor, txt.as_str()); self.cursor += txt.len(); self.max_len += txt.len(); self.redraw(); } fn current_hist(&mut self) -> String { let i: usize = self.history_inx.unwrap(); let line = self.history.get(self.history.len().saturating_sub(i+1)); return line.unwrap().to_string(); } fn set_current_hist(&mut self, str: String) { let i: usize = self.history_inx.unwrap(); let len = self.history.len(); self.history[len.saturating_sub(i+1)] = str; } fn insert_at_cursor_hist(&mut self, txt: String) { let mut line = self.current_hist(); line.insert_str(self.cursor, txt.as_str()); self.history_reset(); self.set_line(line.clone()); self.redraw(); } fn input_char(&mut self, c: char) { if self.history_inx.is_some() { self.insert_at_cursor_hist(String::from(c)); } else { self.insert_at_cursor(String::from(c)); } self.redraw(); } fn input_line(&mut self) -> Result { let mut esc_buf: [u8; 1] = [0]; let mut read_stat = esc_parse::SequenceState::new(); let mut out_buf: Vec = Vec::new(); unsafe { use libc::{read, c_void}; self.redraw(); loop { read(0, esc_buf.as_mut_ptr() as *mut c_void, 1); let res = read_stat.accept_char(esc_buf[0]); if res.is_err() { return Result::Err(ReadError::EscapeError(res.err().unwrap())); } else { if read_stat.has_next() { let chr = read_stat.next().unwrap(); out_buf.push(chr.clone()); if let InputChar::Text(c) = chr { self.input_char(c); } if let InputChar::CursorMotion(m) = chr { use esc_parse::Direction::*; return match m { Up => edit_cmd!(HistoryPrevious), Down => edit_cmd!(HistoryNext), Left => edit_cmd!(CursorBack), Right => edit_cmd!(CursorForward), } } if chr == InputChar::NewLine { break; } if chr == InputChar::Backspace { return edit_cmd!(DeleteBack); } if chr == InputChar::EoT { return edit_cmd!(Exit); } } } } edit_cmd!(NewLine) } } pub fn redraw(&mut self) { hide_cursor(); self.clear(); self.print(format!("{}{}", self.prompt, self.line)); self.move_at(self.cursor); show_cursor(); } pub fn submit_line(&mut self, line: String) { if self.history_inx.is_none() { self.history_append(line.clone()); } else { self.history_reset(); } self.empty(); } pub fn read_line(&mut self) -> Result { use EditCommand::*; loop { let cmd = self.input_line(); if cmd.is_err() { return Err(cmd.err().unwrap()); } let cmd = cmd.ok().unwrap(); match cmd { NewLine => { let line = self.line.clone(); self.submit_line(line.clone()); return Ok(line) }, CursorBack => { self.move_left() }, CursorForward => { self.move_right(); }, DeleteBack => { self.backspace(); }, HistoryPrevious => { self.history_back(); }, HistoryNext => { self.history_front(); }, Exit => return Err(ReadError::Exited) } } } pub fn set_line(&mut self, line: String) { self.line = line.clone(); self.clear(); self.cursor_at(line.len()); } pub fn history_append(&mut self, line: String) { self.history.push(line); self.history_inx = None; } fn history_reset(&mut self) { self.history_inx = None; self.history_saved = None; } pub fn set_prompt(&mut self, prompt: String) { self.prompt = prompt; self.redraw(); } pub fn history_back(&mut self) { let h = self.history.clone(); let mut i = self.history_inx; if i.map_or(false, |v| v+1 == h.len()) { bell(); return; } if i.is_none() { i = Some(0); self.history_saved = Some(self.line.clone()); } else { i = i.map(|v| { v + 1 }); } self.history_inx = i; let i: usize = i.unwrap(); let line = h.get(h.len().saturating_sub(i+1)); if line.is_some() { self.set_line(line.unwrap().to_string()); } else { bell(); self.redraw(); } } fn saved_restore(&mut self) { self.set_line(self.history_saved.clone().expect("Missing saved value")); } pub fn history_front(&mut self) { let h = self.history.clone(); let mut i = self.history_inx; if i.is_none() { bell(); return; } else if i.map_or(false, |v| v == 0) { self.saved_restore(); self.history_reset(); return; } else { i = i.map(|v| { v - 1 }); self.history_inx = i; } let i = i.unwrap(); let line = h.get(h.len().saturating_sub(i+1)); if line.is_some() { self.set_line(line.unwrap().to_string()); } else { bell(); self.redraw(); } } pub fn move_left(&mut self) { use esc_parse::{cursor_left, bell}; if self.cursor > 0 { self.cursor -= 1; cursor_left(); self.redraw(); } else { bell(); } } pub fn move_right(&mut self) { use esc_parse::{cursor_right, bell}; if self.cursor < self.line.len() { self.cursor += 1; cursor_right(); self.redraw(); } else { bell(); } } fn move_at(&mut self, inx: usize) { self.cursor = inx; if self.max_len < inx { self.max_len = inx; } esc_parse::cursor_left_nr(self.max_len); esc_parse::cursor_right_nr(inx+self.prompt.len()); } pub fn cursor_at(&mut self, inx: usize) { self.move_at(inx); self.redraw(); } pub fn backspace(&mut self) { if self.history_inx.is_some() { if self.cursor > 0 { let mut line = self.current_hist(); line.remove(self.cursor-1); self.move_left(); self.history_reset(); self.set_line(line); } else { bell(); } } else { if self.cursor > 0 { self.line.remove(self.cursor-1); self.move_left(); } else { bell(); } } } }