You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
9.5 KiB
340 lines
9.5 KiB
#![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<String>,
|
|
history_inx: Option<usize>,
|
|
history_saved: Option<String>,
|
|
}
|
|
|
|
#[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::<EditCommand, ReadError>::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<EditCommand, ReadError> {
|
|
let mut esc_buf: [u8; 1] = [0];
|
|
let mut read_stat = esc_parse::SequenceState::new();
|
|
let mut out_buf: Vec<esc_parse::InputChar> = 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<String, ReadError> {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
} |