#![forbid(unsafe_code)] use crate::terminal::prelude::*; use crate::terminal::decoding::CharBufReader; use nix::sys::termios::{self, Termios}; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::Arc; use tokio::io::{self, AsyncRead, AsyncWriteExt}; use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::{mpsc, Mutex}; use tokio::task; pub mod decoding; pub mod error; pub mod prelude; #[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); } } pub fn as_string(&self) -> String { self.lines.join("\n") } } pub enum Input { String(String), End, } #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] enum InputAction { Backspace, Execute, } pub struct Terminal { reader: Arc>>, line_buffer: LineBuffer, file_descriptor: RawFd, initial_termios: Termios, interrupt_receiver: mpsc::Receiver<()>, } async fn handle_signals(interrupt_sender: mpsc::Sender<()>) -> Result<()> { let mut stream = signal(SignalKind::interrupt()).map_err(error::internal)?; /* TODO make it work on other signals: SignalKind::hangup(); SignalKind::interrupt(); SignalKind::terminate(); SignalKind::quit(); */ loop { stream.recv().await; interrupt_sender.send(()).await.map_err(error::internal)?; } } impl Terminal { pub fn init(input_stream: InputStream) -> Result> { let (interrupt_sender, interrupt_receiver) = mpsc::channel(1); let _ = task::spawn(handle_signals(interrupt_sender)); let fd = input_stream.as_raw_fd(); let termios = termios::tcgetattr(fd).map_err(error::mode_setting)?; let reader = Arc::new(Mutex::new(CharBufReader::new(input_stream))); let line_buffer = LineBuffer::new(); let terminal = Terminal { reader: reader, line_buffer: line_buffer, file_descriptor: fd, initial_termios: termios, interrupt_receiver: interrupt_receiver, }; terminal.init_modes()?; Ok(terminal) } pub fn cleanup(self) -> Result<()> { self.cleanup_modes()?; Ok(()) } fn init_modes(&self) -> Result<()> { let mut termios = self.initial_termios.clone(); termios.local_flags.remove(termios::LocalFlags::ECHO); termios.local_flags.remove(termios::LocalFlags::ECHONL); termios.local_flags.remove(termios::LocalFlags::ECHOCTL); termios.local_flags.remove(termios::LocalFlags::ICANON); termios.local_flags.insert(termios::LocalFlags::ISIG); termios.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; termios.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; termios::tcsetattr(self.file_descriptor, termios::SetArg::TCSANOW, &termios) .map_err(error::mode_setting)?; Ok(()) } pub fn cleanup_modes(&self) -> Result<()> { let termios = self.initial_termios.clone(); termios::tcsetattr(self.file_descriptor, termios::SetArg::TCSANOW, &termios) .map_err(error::mode_setting)?; Ok(()) } pub async fn handle_input(&mut self) -> Result { loop { let mut action: Option = None; let string = tokio::select! { result = CharBufReader::fill_buf(Arc::clone(&self.reader)) => { let string: String = result.map_err(error::input)?; string } _ = self.interrupt_receiver.recv() => { return Ok(Input::End); } }; let mut chars = string.char_indices(); for (_, c) in &mut chars { match c { '\n' => { action = Some(InputAction::Execute); } '\u{7f}' => { action = Some(InputAction::Backspace); } _ => { self.line_buffer.insert(&c.to_string()); let mut stdout = io::stdout(); stdout.write_all(format!("{}", c).as_bytes()).await .map_err(error::internal)?; stdout.flush().await.map_err(error::internal)?; } } if action.is_some() { break; } } let n_to_consume = match chars.next() { Some((offset, _)) => offset, None => string.len(), }; Arc::get_mut(&mut self.reader).unwrap().lock().await.consume(n_to_consume); match action { Some(InputAction::Execute) => { break; } Some(InputAction::Backspace) => { self.line_buffer.backspace(); } None => { } } } println!("line buffer {:?}", self.line_buffer); let input = Input::String(self.line_buffer.as_string()); Ok(input) } } #[cfg(test)] mod tests { use super::*; use io::Cursor; #[test] fn test_empty_input() { let buffer = Cursor::new(vec![]); let result = handle_input(buffer); assert!(result.is_ok()); } }