summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@gmail.com>2021-03-11 20:15:47 -0800
committerIrene Knapp <ireneista@gmail.com>2021-03-11 20:15:47 -0800
commit86990efcf8fa63decb05f462d745b95e4992dc77 (patch)
tree46990fd1c749ccca0b6477650973d83174e5e264 /src
parenta159d4dd29491a1eb88163e63ceb41a903dc47b6 (diff)
refactor everything into smaller modules; move to using async-std; make the read loop async. some stuff doesn't work yet but it needed to be done and this is as clean a state as it's likely to get in, so we're committing it as a base to build on.
Diffstat (limited to 'src')
-rw-r--r--src/error.rs33
-rw-r--r--src/main.rs61
-rw-r--r--src/path.rs21
-rw-r--r--src/path/error.rs28
-rw-r--r--src/path/prelude.rs5
-rw-r--r--src/prelude.rs5
-rw-r--r--src/result.rs2
-rw-r--r--src/terminal.rs216
-rw-r--r--src/terminal/decoding.rs151
-rw-r--r--src/terminal/error.rs14
-rw-r--r--src/terminal/prelude.rs4
11 files changed, 372 insertions, 168 deletions
diff --git a/src/error.rs b/src/error.rs
index b8289f6..1d291ed 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,5 +1,7 @@
+#![forbid(unsafe_code)]
+
 use crate::path::GenericPath;
-use crate::path::error::{FileNameError, DirectoryNameError};
+use crate::path::error::{FileNameError, DirectoryNameError, PathError};
 use crate::terminal::error::TerminalError;
 
 
@@ -12,10 +14,7 @@ pub enum Error {
   Parse(String),
   FileName(FileNameError),
   DirectoryName(DirectoryNameError),
-  PathListHasEmptyComponents(String),
-  PathLexicallyDirectory(GenericPath),
-  PathLexicallyRelative(GenericPath),
-  PathLexicallyInvalid(GenericPath),
+  Path(PathError),
   PathEmpiricallyFile(GenericPath),
   Terminal(TerminalError),
 }
@@ -29,23 +28,7 @@ impl std::fmt::Display for Error {
       Error::Parse(e) => e.fmt(f),
       Error::FileName(e) => e.fmt(f),
       Error::DirectoryName(e) => e.fmt(f),
-      Error::PathListHasEmptyComponents(path_list) =>
-        f.write_fmt(format_args!(
-            "Path list has empty components: {}",
-            path_list)),
-      Error::PathLexicallyDirectory(path) =>
-        f.write_fmt(format_args!(
-            "The path {} ends in a slash, but is supposed to refer to a file, \
-             not a directory.",
-            path)),
-      Error::PathLexicallyRelative(path) =>
-        f.write_fmt(format_args!(
-            "The path {} is relative, not absolute.",
-            path)),
-      Error::PathLexicallyInvalid(path) =>
-        f.write_fmt(format_args!(
-            "This isn't a valid path. {}",
-            path)),
+      Error::Path(e) => e.fmt(f),
       Error::PathEmpiricallyFile(path) =>
         f.write_fmt(format_args!(
             "There's a file at {}, not a directory.",
@@ -85,6 +68,12 @@ impl From<DirectoryNameError> for Error {
   }
 }
 
+impl From<PathError> for Error {
+  fn from(e: PathError) -> Error {
+    Error::Path(e)
+  }
+}
+
 impl From<TerminalError> for Error {
   fn from(e: TerminalError) -> Error {
     Error::Terminal(e)
diff --git a/src/main.rs b/src/main.rs
index d7a2aff..935155c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,10 +1,15 @@
+#![forbid(unsafe_code)]
 use crate::prelude::*;
+
+use crate::result::Result;
+use crate::terminal::{Input, Terminal};
+
+use async_std::io;
 use std::collections::HashMap;
 use std::collections::HashSet;
-use std::io;
-use std::io::prelude::*;
+use std::env;
 use std::os::unix::fs::PermissionsExt;
-use std::process::Command;
+use std::process::{self, Command};
 
 #[macro_use] extern crate lalrpop_util;
 
@@ -16,14 +21,9 @@ pub mod result;
 pub mod terminal;
 
 
-pub enum Input {
-  String(String),
-  End,
-}
-
-
 fn main() -> Result<()> {
-  std::process::exit(match repl() {
+  let result = async_std::task::block_on(async { repl().await });
+  process::exit(match result {
     Ok(()) => 0,
     Err(ref e) => {
       eprintln!("{}", e);
@@ -33,45 +33,42 @@ fn main() -> Result<()> {
 }
 
 
-fn repl() -> Result<()> {
+async fn repl() -> Result<()> {
   println!("Hello, terminal!");
 
+  let mut terminal = Terminal::init(io::stdin())?;
+
   loop {
-    prompt()?;
+    prompt().await?;
+
+    let input = terminal.handle_input().await?;
 
-    terminal::handle_input_terminal(io::stdin())?;
-    let input = read()?;
     match input {
-      Input::String(string) => execute(&string)?,
+      Input::String(string) => {
+        println!("{:?} {}", string, string.len());
+        execute(&string).await?
+      },
       Input::End => break,
     }
+
+    break;
   }
 
+  terminal.cleanup()?;
+
   Ok(())
 }
 
 
-fn prompt() -> Result<()> {
+async fn prompt() -> Result<()> {
   print!("\n$ ");
-  io::stdout().flush()?;
+  io::stdout().flush().await?;
 
   Ok(())
 }
 
 
-fn read() -> Result<Input> {
-  let mut input = String::new();
-  let n_bytes = io::stdin().read_line(&mut input)?;
-
-  if n_bytes == 0 {
-    Ok(Input::End)
-  } else {
-    Ok(Input::String(input))
-  }
-}
-
-
-fn execute(input: &str) -> Result<()> {
+async fn execute(input: &str) -> Result<()> {
   let invocation = commandline::InvocationParser::new().parse(input)?;
 
   match invocation.as_slice() {
@@ -114,12 +111,12 @@ fn execute(input: &str) -> Result<()> {
 
 
 fn read_environment() -> Result<HashMap<String,String>> {
-  Ok(std::env::vars().collect())
+  Ok(env::vars().collect())
 }
 
 
 fn get_environment(variable_name: &str) -> Result<Option<String>> {
-  Ok(std::env::vars()
+  Ok(env::vars()
      .find(|(key, _)| key == variable_name)
      .map(|(_, value)| value))
 }
diff --git a/src/path.rs b/src/path.rs
index 570b8ef..7df10b2 100644
--- a/src/path.rs
+++ b/src/path.rs
@@ -6,14 +6,15 @@
  * to get the benefit of its functionality for handling non-Unicode filenames.
  */
 
-use crate::prelude::*;
-use crate::path::error::*;
+#![forbid(unsafe_code)]
+use crate::path::prelude::*;
 
 use std::str::FromStr;
 
 
 lalrpop_mod!(pub parser, "/path/parser.rs");
 pub mod error;
+pub mod prelude;
 
 
 #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)]
@@ -230,10 +231,10 @@ pub fn parse_path_list(path_list: &str)
         .parse(path_list)
       {
         Ok(_) => {
-          Err(Error::PathListHasEmptyComponents(path_list.to_string()))
+          Err(PathError::PathListHasEmptyComponents(path_list.to_string()))
         },
         Err(_) => {
-          Err(Error::Parse(original_error.to_string()))
+          Err(PathError::Parse(original_error.to_string()))
         },
       }
     },
@@ -245,7 +246,7 @@ pub fn absolute_directory_path(generic_path: GenericPath)
   -> Result<AbsoluteDirectoryPath>
 {
   if !generic_path.starts_with_slash {
-    return Err(Error::PathLexicallyRelative(generic_path));
+    return Err(PathError::PathLexicallyRelative(generic_path));
   }
 
   let mut flattened_components = Vec::new();
@@ -256,7 +257,7 @@ pub fn absolute_directory_path(generic_path: GenericPath)
         if flattened_components.len() > 0 {
           flattened_components.pop();
         } else {
-          return Err(Error::PathLexicallyInvalid(generic_path));
+          return Err(PathError::PathLexicallyInvalid(generic_path));
         }
       },
       GenericPathComponent::FileOrDirectoryName(name) => {
@@ -275,11 +276,11 @@ pub fn absolute_file_path(generic_path: GenericPath)
   -> Result<AbsoluteFilePath>
 {
   if !generic_path.starts_with_slash {
-    return Err(Error::PathLexicallyRelative(generic_path));
+    return Err(PathError::PathLexicallyRelative(generic_path));
   }
 
   if generic_path.ends_with_slash {
-    return Err(Error::PathLexicallyDirectory(generic_path));
+    return Err(PathError::PathLexicallyDirectory(generic_path));
   }
 
   let mut iterator = generic_path.components.iter();
@@ -289,7 +290,7 @@ pub fn absolute_file_path(generic_path: GenericPath)
       FileName(name.to_string())
     }
     _ => {
-      return Err(Error::PathLexicallyInvalid(generic_path));
+      return Err(PathError::PathLexicallyInvalid(generic_path));
     }
   };
 
@@ -301,7 +302,7 @@ pub fn absolute_file_path(generic_path: GenericPath)
         if flattened_components.len() > 0 {
           flattened_components.pop();
         } else {
-          return Err(Error::PathLexicallyInvalid(generic_path));
+          return Err(PathError::PathLexicallyInvalid(generic_path));
         }
       },
       GenericPathComponent::FileOrDirectoryName(name) => {
diff --git a/src/path/error.rs b/src/path/error.rs
index 0ee5423..aee6e9f 100644
--- a/src/path/error.rs
+++ b/src/path/error.rs
@@ -1,3 +1,10 @@
+#![forbid(unsafe_code)]
+
+use crate::path::GenericPath;
+
+pub type Result<T> = std::result::Result<T, PathError>;
+
+
 #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)]
 pub enum FileNameError {
   ContainsSlash(String),
@@ -11,6 +18,10 @@ pub enum DirectoryNameError {
 #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)]
 pub enum PathError {
   Parse(String),
+  PathLexicallyDirectory(GenericPath),
+  PathLexicallyRelative(GenericPath),
+  PathLexicallyInvalid(GenericPath),
+  PathListHasEmptyComponents(String),
 }
 
 
@@ -45,6 +56,23 @@ impl std::fmt::Display for PathError {
     match self {
       PathError::Parse(s) =>
         f.write_fmt(format_args!("Syntax error in path: {}", s)),
+      PathError::PathLexicallyDirectory(path) =>
+        f.write_fmt(format_args!(
+            "The path {} ends in a slash, but is supposed to refer to a file, \
+             not a directory.",
+            path)),
+      PathError::PathLexicallyRelative(path) =>
+        f.write_fmt(format_args!(
+            "The path {} is relative, not absolute.",
+            path)),
+      PathError::PathLexicallyInvalid(path) =>
+        f.write_fmt(format_args!(
+            "This isn't a valid path. {}",
+            path)),
+      PathError::PathListHasEmptyComponents(path_list) =>
+        f.write_fmt(format_args!(
+            "Path list has empty components: {}",
+            path_list)),
     }
   }
 }
diff --git a/src/path/prelude.rs b/src/path/prelude.rs
new file mode 100644
index 0000000..f7b23dd
--- /dev/null
+++ b/src/path/prelude.rs
@@ -0,0 +1,5 @@
+#![forbid(unsafe_code)]
+
+pub use crate::path::error::{
+    Result, DirectoryNameError, FileNameError, PathError};
+
diff --git a/src/prelude.rs b/src/prelude.rs
index b44fe02..f678ba1 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -1,2 +1,3 @@
-pub use crate::error::Error;
-pub use crate::result::Result;
+#![forbid(unsafe_code)]
+pub use async_std::prelude::*;
+
diff --git a/src/result.rs b/src/result.rs
index 8207680..b757636 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -1,3 +1,5 @@
+#![forbid(unsafe_code)]
+
 use crate::error::Error;
 
 pub type Result<T> = std::result::Result<T, Error>;
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() {
diff --git a/src/terminal/decoding.rs b/src/terminal/decoding.rs
index e2654fb..11f6046 100644
--- a/src/terminal/decoding.rs
+++ b/src/terminal/decoding.rs
@@ -1,63 +1,128 @@
-use crate::terminal::error::{self, TerminalError};
+#![forbid(unsafe_code)]
+use crate::terminal::prelude::*;
 
-use std::io::{BufRead, BufReader, Read};
+use crate::terminal::error;
+
+use async_std::future::Future;
+use async_std::io::{BufRead, BufReader, Read};
+use async_std::sync::{Arc, Mutex};
+use async_std::task::{Context, Poll};
+use pin_project::pin_project;
+use pin_utils::pin_mut;
+use std::pin::Pin;
 use std::str;
+use std::sync::atomic::{AtomicBool, Ordering};
 
 
-pub struct CharBufReader<R: Read> {
-  byte_reader: BufReader<R>,
-  char_buffer: String,
+#[pin_project]
+pub struct CharBufReader<R: Read + Unpin> {
+  #[pin] byte_reader: BufReader<R>,
+  #[pin] char_buffer: String,
+  is_interrupted: Arc<AtomicBool>,
 }
 
 
-impl<R: Read> CharBufReader<R> {
-  pub fn new(input_stream: R) -> CharBufReader<R> {
-    let byte_reader = BufReader::new(input_stream);
+#[pin_project]
+struct FillBufFuture<R: Read + Unpin> {
+  char_reader: Arc<Mutex<CharBufReader<R>>>,
+  //char_reader: Arc<CharBufReader<R>>,
+}
 
-    CharBufReader {
-      byte_reader: byte_reader,
-      char_buffer: String::new(),
-    }
-  }
 
-  pub fn fill_buf(&mut self)
-    -> std::result::Result<&str, TerminalError>
+impl<R: Read + Unpin> Future for FillBufFuture<R> {
+  type Output = Result<String>;
+
+  fn poll(self: Pin<&mut Self>, context: &mut Context<'_>)
+    -> Poll<Self::Output>
   {
-    loop {
-      let byte_buffer = self.byte_reader.fill_buf().map_err(error::input)?;
-
-      match str::from_utf8(byte_buffer) {
-        Err(error) => {
-          let n_valid = error.valid_up_to();
-          if n_valid == 0 {
-            self.byte_reader.consume(1);
-          } else {
-            match str::from_utf8(&byte_buffer[..n_valid]) {
-              Err(_) => {
-                self.byte_reader.consume(1);
-              },
-              Ok(chars) => {
-                self.char_buffer.push_str(chars);
-
-                self.byte_reader.consume(n_valid);
-
-                break;
-              },
-            }
+    let future = self.project();
+    let char_reader: &mut Arc<Mutex<CharBufReader<R>>> = future.char_reader;
+    let char_reader_future = char_reader.lock();
+    pin_mut!(char_reader_future);
+    match char_reader_future.poll(context) {
+      Poll::Ready(mut char_reader) => {
+        let char_reader = &mut *char_reader;
+        let mut byte_reader: Pin<&mut BufReader<R>> = Pin::new(&mut char_reader.byte_reader);
+
+        loop {
+          if char_reader.is_interrupted.load(Ordering::Relaxed) {
+            println!("char reader got interrupt");
+            return Poll::Pending;
           }
-        },
-        Ok(chars) => {
-          self.char_buffer.push_str(chars);
 
-          let n_to_consume = byte_buffer.len();
-          self.byte_reader.consume(n_to_consume);
+          println!("about to fill_buf");
+          match byte_reader.as_mut().poll_fill_buf(context).map_err(error::input)?
+          {
+            Poll::Ready(byte_buffer) => {
+              println!("done with fill_buf");
+              match str::from_utf8(&byte_buffer) {
+                Err(error) => {
+                  let n_valid = error.valid_up_to();
+                  if n_valid == 0 {
+                    byte_reader.as_mut().consume(1);
+                  } else {
+                    match str::from_utf8(&byte_buffer[..n_valid]) {
+                      Err(_) => {
+                        byte_reader.as_mut().consume(1);
+                      },
+                      Ok(chars) => {
+                        char_reader.char_buffer.push_str(chars);
+
+                        byte_reader.as_mut().consume(n_valid);
+
+                        break;
+                      },
+                    }
+                  }
+                }
+                Ok(chars) => {
+                  char_reader.char_buffer.push_str(chars);
 
-          break;
+                  let n_to_consume = byte_buffer.len();
+                  byte_reader.as_mut().consume(n_to_consume);
+
+                  break;
+                }
+              }
+            }
+            Poll::Pending => {
+              println!("fill_buf pending");
+              return Poll::Pending;
+            }
+          }
         }
+
+        return Poll::Ready(Ok(char_reader.char_buffer.to_string()));
+      }
+      Poll::Pending => {
+        println!("char_reader mutex pending");
+        return Poll::Pending;
       }
     }
+  }
+}
+
+
+impl<R: Read + Unpin> CharBufReader<R> {
+  pub fn new(input_stream: R, is_interrupted: Arc<AtomicBool>)
+    -> CharBufReader<R>
+  {
+    let byte_reader = BufReader::new(input_stream);
+
+    CharBufReader {
+      byte_reader: byte_reader,
+      char_buffer: String::new(),
+      is_interrupted: is_interrupted,
+    }
+  }
 
-    return Ok(&self.char_buffer);
+  pub fn fill_buf(reader: Arc<Mutex<Self>>)
+  //pub fn fill_buf(reader: Arc<Self>)
+    -> impl Future<Output = Result<String>>
+  {
+    FillBufFuture {
+      char_reader: reader,
+    }
   }
 
   pub fn consume(&mut self, amount: usize) {
diff --git a/src/terminal/error.rs b/src/terminal/error.rs
index 6666e49..795f973 100644
--- a/src/terminal/error.rs
+++ b/src/terminal/error.rs
@@ -1,7 +1,13 @@
+#![forbid(unsafe_code)]
+
+pub type Result<T> = std::result::Result<T, TerminalError>;
+
+
 #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)]
 pub enum TerminalError {
   Input(String),
   ModeSetting(String),
+  Internal(String),
 }
 
 impl std::error::Error for TerminalError { }
@@ -15,6 +21,9 @@ impl std::fmt::Display for TerminalError {
       TerminalError::ModeSetting(s) =>
         f.write_fmt(format_args!(
             "Can't set terminal mode: {}", s)),
+      TerminalError::Internal(s) =>
+        f.write_fmt(format_args!(
+            "Internal error regarding the terminal: {}", s)),
     }
   }
 }
@@ -29,3 +38,8 @@ pub fn mode_setting(e: impl std::error::Error) -> TerminalError {
   TerminalError::ModeSetting(format!("{}", e))
 }
 
+
+pub fn internal(e: impl std::error::Error) -> TerminalError {
+  TerminalError::Internal(format!("{}", e))
+}
+
diff --git a/src/terminal/prelude.rs b/src/terminal/prelude.rs
new file mode 100644
index 0000000..bada817
--- /dev/null
+++ b/src/terminal/prelude.rs
@@ -0,0 +1,4 @@
+#![forbid(unsafe_code)]
+
+pub use crate::terminal::error::{Result, TerminalError};
+