summary refs log tree commit diff
path: root/src/terminal.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal.rs')
-rw-r--r--src/terminal.rs216
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() {