From f25a79763b9ae493598230a109eb0788def17199 Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Tue, 12 Mar 2024 22:06:24 -0700 Subject: separate the main from the library, move it to an example Change-Id: I3478f222ee4b24d9d1796f0f58986186119bc2f7 --- examples/line-input/error.rs | 40 +++++++ examples/line-input/main.rs | 63 ++++++++++ src/decoding.rs | 117 +++++++++++++++++++ src/error.rs | 48 ++++---- src/lib.rs | 271 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 65 ----------- src/prelude.rs | 4 + src/result.rs | 5 - src/terminal.rs | 271 ------------------------------------------- src/terminal/decoding.rs | 117 ------------------- src/terminal/error.rs | 45 ------- src/terminal/prelude.rs | 4 - 12 files changed, 522 insertions(+), 528 deletions(-) create mode 100644 examples/line-input/error.rs create mode 100644 examples/line-input/main.rs create mode 100644 src/decoding.rs create mode 100644 src/lib.rs delete mode 100644 src/main.rs create mode 100644 src/prelude.rs delete mode 100644 src/result.rs delete mode 100644 src/terminal.rs delete mode 100644 src/terminal/decoding.rs delete mode 100644 src/terminal/error.rs delete mode 100644 src/terminal/prelude.rs diff --git a/examples/line-input/error.rs b/examples/line-input/error.rs new file mode 100644 index 0000000..88e2795 --- /dev/null +++ b/examples/line-input/error.rs @@ -0,0 +1,40 @@ +#![forbid(unsafe_code)] + +use line_input::error::TerminalError; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + IO(std::io::Error), + Terminal(TerminalError), +} + +impl std::error::Error for Error { } + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::IO(e) => e.fmt(f), + Error::Terminal(e) => e.fmt(f), + } + } +} + +impl From<()> for Error { + fn from(_: ()) -> Error { + unreachable!() + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Error { + Error::IO(e) + } +} + +impl From for Error { + fn from(e: TerminalError) -> Error { + Error::Terminal(e) + } +} diff --git a/examples/line-input/main.rs b/examples/line-input/main.rs new file mode 100644 index 0000000..5da00fe --- /dev/null +++ b/examples/line-input/main.rs @@ -0,0 +1,63 @@ +#![forbid(unsafe_code)] +use crate::error::Result; +use line_input::{Input, Terminal}; + +use std::process; +use tokio::io::{self, AsyncWriteExt}; + +pub mod error; + + +#[tokio::main] +async fn main() -> Result<()> { + let result = repl().await; + process::exit(match result { + Ok(()) => 0, + Err(ref e) => { + eprintln!("{}", e); + 1 + } + }) +} + + +async fn repl() -> Result<()> { + println!("Hello, terminal!"); + + let mut terminal = Terminal::init(io::stdin())?; + + loop { + prompt().await?; + + let input = terminal.handle_input().await?; + + match input { + Input::String(string) => { + println!("{:?} {}", string, string.len()); + execute(&string).await? + }, + Input::End => break, + } + + break; + } + + terminal.cleanup()?; + + Ok(()) +} + + +async fn prompt() -> Result<()> { + let mut stdout = io::stdout(); + stdout.write_all("\n$ ".as_bytes()).await?; + stdout.flush().await?; + + Ok(()) +} + + +async fn execute(_input: &str) -> Result<()> { + Ok(()) +} + diff --git a/src/decoding.rs b/src/decoding.rs new file mode 100644 index 0000000..cba4d76 --- /dev/null +++ b/src/decoding.rs @@ -0,0 +1,117 @@ +#![forbid(unsafe_code)] +use crate::prelude::*; + +use crate::error; + +use pin_project::pin_project; +use pin_utils::pin_mut; +use std::future::Future; +use std::pin::Pin; +use std::str; +use std::sync::Arc; +use std::task::{Context, Poll}; +use tokio::io::{AsyncBufRead, AsyncRead, BufReader}; +use tokio::sync::Mutex; + + +#[pin_project] +pub struct CharBufReader { + #[pin] byte_reader: BufReader, + #[pin] char_buffer: String, +} + + +#[pin_project] +struct FillBufFuture { + char_reader: Arc>>, +} + + +impl Future for FillBufFuture { + type Output = Result; + + fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) + -> Poll + { + let future = self.project(); + let char_reader: &mut Arc>> = 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> = Pin::new(&mut char_reader.byte_reader); + + loop { + match byte_reader.as_mut().poll_fill_buf(context).map_err(error::input)? + { + Poll::Ready(byte_buffer) => { + 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); + + let n_to_consume = byte_buffer.len(); + byte_reader.as_mut().consume(n_to_consume); + + break; + } + } + } + Poll::Pending => { + return Poll::Pending; + } + } + } + + return Poll::Ready(Ok(char_reader.char_buffer.to_string())); + } + Poll::Pending => { + return Poll::Pending; + } + } + } +} + + +impl CharBufReader { + pub fn new(input_stream: R) -> CharBufReader { + let byte_reader = BufReader::new(input_stream); + + CharBufReader { + byte_reader: byte_reader, + char_buffer: String::new(), + } + } + + pub fn fill_buf(reader: Arc>) + -> impl Future> + { + FillBufFuture { + char_reader: reader, + } + } + + pub fn consume(&mut self, amount: usize) { + self.char_buffer.replace_range(..amount, ""); + } +} + diff --git a/src/error.rs b/src/error.rs index 426ebf5..795f973 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,39 +1,45 @@ #![forbid(unsafe_code)] -use crate::terminal::error::TerminalError; +pub type Result = std::result::Result; -#[derive(Debug)] -pub enum Error { - IO(std::io::Error), - Terminal(TerminalError), +#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] +pub enum TerminalError { + Input(String), + ModeSetting(String), + Internal(String), } -impl std::error::Error for Error { } +impl std::error::Error for TerminalError { } -impl std::fmt::Display for Error { +impl std::fmt::Display for TerminalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Error::IO(e) => e.fmt(f), - Error::Terminal(e) => e.fmt(f), + TerminalError::Input(s) => + f.write_fmt(format_args!( + "Can't read terminal input: {}", s)), + 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)), } } } -impl From<()> for Error { - fn from(_: ()) -> Error { - unreachable!() - } + +pub fn input(e: impl std::error::Error) -> TerminalError { + TerminalError::ModeSetting(format!("{}", e)) } -impl From for Error { - fn from(e: std::io::Error) -> Error { - Error::IO(e) - } + +pub fn mode_setting(e: impl std::error::Error) -> TerminalError { + TerminalError::ModeSetting(format!("{}", e)) } -impl From for Error { - fn from(e: TerminalError) -> Error { - Error::Terminal(e) - } + +pub fn internal(e: impl std::error::Error) -> TerminalError { + TerminalError::Internal(format!("{}", e)) } + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3bcec63 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,271 @@ +#![forbid(unsafe_code)] +use crate::prelude::*; + +use crate::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 fn cursor_to_end_of_line(&self) -> &str { + &self.lines[self.cursor.y][self.cursor.x ..] + } +} + + +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 + { + let mut stdout = io::stdout(); + + 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()); + + 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(); + + stdout.write_all(format!("\u{08}\u{1b}[1X{}", + self.line_buffer.cursor_to_end_of_line()) + .as_bytes()).await.map_err(error::internal)?; + stdout.flush().await.map_err(error::internal)?; + } + 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 std::io::Cursor; + + #[test] + fn test_empty_input() { + let buffer = Cursor::new(vec![]); + let terminal = Terminal::init(buffer).unwrap(); + let result = terminal.handle_input(); + assert!(result.is_ok()); + } +} +*/ diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 9d8ed49..0000000 --- a/src/main.rs +++ /dev/null @@ -1,65 +0,0 @@ -#![forbid(unsafe_code)] -use crate::result::Result; -use crate::terminal::{Input, Terminal}; - -use std::process; -use tokio::io::{self, AsyncWriteExt}; - -pub mod error; -pub mod result; -pub mod terminal; - - -#[tokio::main] -async fn main() -> Result<()> { - let result = repl().await; - process::exit(match result { - Ok(()) => 0, - Err(ref e) => { - eprintln!("{}", e); - 1 - } - }) -} - - -async fn repl() -> Result<()> { - println!("Hello, terminal!"); - - let mut terminal = Terminal::init(io::stdin())?; - - loop { - prompt().await?; - - let input = terminal.handle_input().await?; - - match input { - Input::String(string) => { - println!("{:?} {}", string, string.len()); - execute(&string).await? - }, - Input::End => break, - } - - break; - } - - terminal.cleanup()?; - - Ok(()) -} - - -async fn prompt() -> Result<()> { - let mut stdout = io::stdout(); - stdout.write_all("\n$ ".as_bytes()).await?; - stdout.flush().await?; - - Ok(()) -} - - -async fn execute(_input: &str) -> Result<()> { - Ok(()) -} - diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..87b1f77 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,4 @@ +#![forbid(unsafe_code)] + +pub use crate::error::{Result, TerminalError}; + diff --git a/src/result.rs b/src/result.rs deleted file mode 100644 index b757636..0000000 --- a/src/result.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![forbid(unsafe_code)] - -use crate::error::Error; - -pub type Result = std::result::Result; diff --git a/src/terminal.rs b/src/terminal.rs deleted file mode 100644 index 405a719..0000000 --- a/src/terminal.rs +++ /dev/null @@ -1,271 +0,0 @@ -#![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 fn cursor_to_end_of_line(&self) -> &str { - &self.lines[self.cursor.y][self.cursor.x ..] - } -} - - -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 - { - let mut stdout = io::stdout(); - - 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()); - - 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(); - - stdout.write_all(format!("\u{08}\u{1b}[1X{}", - self.line_buffer.cursor_to_end_of_line()) - .as_bytes()).await.map_err(error::internal)?; - stdout.flush().await.map_err(error::internal)?; - } - 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 std::io::Cursor; - - #[test] - fn test_empty_input() { - let buffer = Cursor::new(vec![]); - let terminal = Terminal::init(buffer).unwrap(); - let result = terminal.handle_input(); - assert!(result.is_ok()); - } -} -*/ diff --git a/src/terminal/decoding.rs b/src/terminal/decoding.rs deleted file mode 100644 index 018730e..0000000 --- a/src/terminal/decoding.rs +++ /dev/null @@ -1,117 +0,0 @@ -#![forbid(unsafe_code)] -use crate::terminal::prelude::*; - -use crate::terminal::error; - -use pin_project::pin_project; -use pin_utils::pin_mut; -use std::future::Future; -use std::pin::Pin; -use std::str; -use std::sync::Arc; -use std::task::{Context, Poll}; -use tokio::io::{AsyncBufRead, AsyncRead, BufReader}; -use tokio::sync::Mutex; - - -#[pin_project] -pub struct CharBufReader { - #[pin] byte_reader: BufReader, - #[pin] char_buffer: String, -} - - -#[pin_project] -struct FillBufFuture { - char_reader: Arc>>, -} - - -impl Future for FillBufFuture { - type Output = Result; - - fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) - -> Poll - { - let future = self.project(); - let char_reader: &mut Arc>> = 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> = Pin::new(&mut char_reader.byte_reader); - - loop { - match byte_reader.as_mut().poll_fill_buf(context).map_err(error::input)? - { - Poll::Ready(byte_buffer) => { - 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); - - let n_to_consume = byte_buffer.len(); - byte_reader.as_mut().consume(n_to_consume); - - break; - } - } - } - Poll::Pending => { - return Poll::Pending; - } - } - } - - return Poll::Ready(Ok(char_reader.char_buffer.to_string())); - } - Poll::Pending => { - return Poll::Pending; - } - } - } -} - - -impl CharBufReader { - pub fn new(input_stream: R) -> CharBufReader { - let byte_reader = BufReader::new(input_stream); - - CharBufReader { - byte_reader: byte_reader, - char_buffer: String::new(), - } - } - - pub fn fill_buf(reader: Arc>) - -> impl Future> - { - FillBufFuture { - char_reader: reader, - } - } - - pub fn consume(&mut self, amount: usize) { - self.char_buffer.replace_range(..amount, ""); - } -} - diff --git a/src/terminal/error.rs b/src/terminal/error.rs deleted file mode 100644 index 795f973..0000000 --- a/src/terminal/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![forbid(unsafe_code)] - -pub type Result = std::result::Result; - - -#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] -pub enum TerminalError { - Input(String), - ModeSetting(String), - Internal(String), -} - -impl std::error::Error for TerminalError { } - -impl std::fmt::Display for TerminalError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TerminalError::Input(s) => - f.write_fmt(format_args!( - "Can't read terminal input: {}", s)), - 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)), - } - } -} - - -pub fn input(e: impl std::error::Error) -> TerminalError { - TerminalError::ModeSetting(format!("{}", e)) -} - - -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 deleted file mode 100644 index bada817..0000000 --- a/src/terminal/prelude.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![forbid(unsafe_code)] - -pub use crate::terminal::error::{Result, TerminalError}; - -- cgit 1.4.1