use crate::terminal::decoding::CharBufReader; use crate::terminal::error::TerminalError; use nix::sys::termios; use std::io::Read; use std::os::unix::io::AsRawFd; pub mod decoding; pub mod error; #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] struct Point { x: usize, y: usize, } impl Point { pub fn new(x: usize, y: usize) -> Point { Point { x: x, y: y, } } } #[derive(Debug,Eq,PartialEq)] struct LineBuffer { lines: Vec, cursor: Point, } impl LineBuffer { pub fn new() -> LineBuffer { LineBuffer { lines: vec![String::new()], cursor: Point::new(0, 0), } } pub fn insert(&mut self, input: &str) { let line = &mut self.lines[self.cursor.y]; line.replace_range(self.cursor.x .. self.cursor.x, input); self.cursor.x += input.len(); } pub fn backspace(&mut self) { if self.cursor.x > 0 { let line = &mut self.lines[self.cursor.y]; line.replace_range(self.cursor.x - 1 .. self.cursor.x, ""); self.cursor.x -= 1; } else if self.cursor.y > 0 { let second_line = self.lines.remove(self.cursor.y); let first_line = &mut self.lines[self.cursor.y - 1]; self.cursor.x = first_line.len(); self.cursor.y -= 1; first_line.push_str(&second_line); } } } #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] enum InputAction { Backspace, Execute, } pub fn handle_input_terminal(input_stream: impl Read + AsRawFd) -> std::result::Result<(), TerminalError> { let fd = input_stream.as_raw_fd(); let mut termios = termios::tcgetattr(fd) .map_err(error::mode_setting)?; termios.local_flags.insert(termios::LocalFlags::ECHO); termios.local_flags.remove(termios::LocalFlags::ECHOCTL); termios.local_flags.remove(termios::LocalFlags::ICANON); termios.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; termios.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; termios::tcsetattr(fd, termios::SetArg::TCSANOW, &termios) .map_err(error::mode_setting)?; handle_input(input_stream) } pub fn handle_input(input_stream: impl Read) -> std::result::Result<(), TerminalError> { let mut reader = CharBufReader::new(input_stream); let mut line_buffer = LineBuffer::new(); loop { let mut action: Option = None; let string = reader.fill_buf().map_err(error::input)?; let mut chars = string.char_indices(); for (_, c) in &mut chars { match c { '\n' => { action = Some(InputAction::Execute); } '\u{7f}' => { action = Some(InputAction::Backspace); } _ => { line_buffer.insert(&c.to_string()); } } if action.is_some() { break; } } let n_to_consume = match chars.next() { Some((offset, _)) => offset, None => string.len(), }; reader.consume(n_to_consume); match action { Some(InputAction::Execute) => { break; } Some(InputAction::Backspace) => { line_buffer.backspace(); } None => { } } } println!("line buffer {:?}", line_buffer); Ok(()) } #[cfg(test)] mod tests { use super::*; use std::io::Cursor; #[test] fn test_empty_input() { let buffer = Cursor::new(vec![]); let result = handle_input(buffer); assert!(result.is_ok()); } }