diff options
Diffstat (limited to 'src/terminal.rs')
-rw-r--r-- | src/terminal.rs | 216 |
1 files changed, 157 insertions, 59 deletions
diff --git a/src/terminal.rs b/src/terminal.rs index 298cc8c..73ba035 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,12 +1,19 @@ +#![forbid(unsafe_code)] +use crate::prelude::*; +use crate::terminal::prelude::*; + use crate::terminal::decoding::CharBufReader; -use crate::terminal::error::TerminalError; -use nix::sys::termios; -use std::io::Read; -use std::os::unix::io::AsRawFd; +use async_std::io::{self, Read}; +use async_std::os::unix::io::{AsRawFd, RawFd}; +use async_std::sync::{Arc, Mutex}; +use nix::sys::termios::{self, Termios}; +use signal_hook::consts::TERM_SIGNALS; +use std::sync::atomic::{AtomicBool, Ordering}; pub mod decoding; pub mod error; +pub mod prelude; #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] @@ -60,6 +67,16 @@ impl LineBuffer { first_line.push_str(&second_line); } } + + pub fn as_string(&self) -> String { + self.lines.join("\n") + } +} + + +pub enum Input { + String(String), + End, } @@ -70,83 +87,164 @@ enum InputAction { } -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 struct Terminal<InputStream: Read + Unpin> { + reader: Arc<Mutex<CharBufReader<InputStream>>>, + //reader: Arc<CharBufReader<InputStream>>, + line_buffer: LineBuffer, + file_descriptor: RawFd, + initial_termios: Termios, + is_interrupted: Arc<AtomicBool>, } -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(); +impl<InputStream: Read + AsRawFd + Unpin> Terminal<InputStream> { + pub fn init(input_stream: InputStream) -> Result<Terminal<InputStream>> { + let is_interrupted = Arc::new(AtomicBool::new(false)); - loop { - let mut action: Option<InputAction> = None; + for signal in TERM_SIGNALS { + signal_hook::flag::register(*signal, Arc::clone(&is_interrupted)) + .map_err(error::internal)?; + } - let string = reader.fill_buf().map_err(error::input)?; + 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, Arc::clone(&is_interrupted)))); + //let reader = Arc::new(CharBufReader::new( + //input_stream, Arc::clone(&is_interrupted))); + let line_buffer = LineBuffer::new(); + + let terminal = Terminal { + reader: reader, + line_buffer: line_buffer, + file_descriptor: fd, + initial_termios: termios, + is_interrupted: is_interrupted, + }; - let mut chars = string.char_indices(); + terminal.init_modes()?; - for (_, c) in &mut chars { - match c { - '\n' => { - action = Some(InputAction::Execute); - } - '\u{7f}' => { - action = Some(InputAction::Backspace); - } - _ => { - line_buffer.insert(&c.to_string()); - } - } + Ok(terminal) + } - if action.is_some() { - break; - } - } - let n_to_consume = match chars.next() { - Some((offset, _)) => offset, - None => string.len(), - }; + 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(()) + } - reader.consume(n_to_consume); - match action { - Some(InputAction::Execute) => { + pub fn cleanup_modes(&self) -> Result<()> { + println!("de-initializing"); // DO NOT SUBMIT + + let termios = self.initial_termios.clone(); + + termios::tcsetattr(self.file_descriptor, + termios::SetArg::TCSANOW, + &termios) + .map_err(error::mode_setting)?; + + Ok(()) + } + + + pub fn is_exiting(&self) -> bool { + self.is_interrupted.load(Ordering::Relaxed) + } + + + pub async fn handle_input(&mut self) -> Result<Input> + { + let is_interrupted = Arc::clone(&self.is_interrupted); + + loop { + let mut action: Option<InputAction> = None; + + let string = CharBufReader::fill_buf(Arc::clone(&self.reader)).await.map_err(error::input)?; + + if is_interrupted.load(Ordering::Relaxed) { break; } - Some(InputAction::Backspace) => { - line_buffer.backspace(); + + 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()); + print!("{}", c); + io::stdout().flush(); + } + } + + 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 => { } } - None => { } } - } - println!("line buffer {:?}", line_buffer); - - Ok(()) + println!("line buffer {:?}", self.line_buffer); + if self.is_interrupted.load(Ordering::Relaxed) { + println!("exiting 3"); + Ok(Input::End) + } else { + let input = Input::String(self.line_buffer.as_string()); + Ok(input) + } + } } #[cfg(test)] mod tests { use super::*; - use std::io::Cursor; + use io::Cursor; #[test] fn test_empty_input() { |