aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-06-08 18:11:38 -0400
committerJesse Luehrs <doy@tozt.net>2019-06-08 18:11:38 -0400
commit59abbab62e936a46ceea7bb7ebac5ca9ef4ba2da (patch)
treef9051679b288203a4c05cff097c6738dfda94fa3 /src
parent9168c7fca9043d48bc65eaca6837a537d220a1ed (diff)
downloadnbsh-old-59abbab62e936a46ceea7bb7ebac5ca9ef4ba2da.tar.gz
nbsh-old-59abbab62e936a46ceea7bb7ebac5ca9ef4ba2da.zip
basic readline implementation
Diffstat (limited to 'src')
-rw-r--r--src/main.rs7
-rw-r--r--src/readline.rs169
-rw-r--r--src/repl.rs53
3 files changed, 228 insertions, 1 deletions
diff --git a/src/main.rs b/src/main.rs
index e7a11a9..105450f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,8 @@
+mod readline;
+mod repl;
+
fn main() {
- println!("Hello, world!");
+ let _screen = crossterm::RawScreen::into_raw_mode().unwrap();
+
+ repl::repl();
}
diff --git a/src/readline.rs b/src/readline.rs
new file mode 100644
index 0000000..73b5195
--- /dev/null
+++ b/src/readline.rs
@@ -0,0 +1,169 @@
+use std::io::Write;
+
+#[derive(Debug)]
+pub enum Error {
+ EOF,
+ IOError(std::io::Error),
+}
+
+pub struct Readline {
+ reader: Option<KeyReader>,
+ buffer: String,
+ prompt: String,
+ wrote_prompt: bool,
+ echo: bool,
+}
+
+impl Readline {
+ fn process_event(
+ &mut self,
+ event: crossterm::InputEvent,
+ ) -> std::result::Result<futures::Async<String>, Error> {
+ match event {
+ crossterm::InputEvent::Keyboard(e) => {
+ return self.process_keyboard_event(e)
+ }
+ _ => {}
+ }
+ return Ok(futures::Async::NotReady);
+ }
+
+ fn process_keyboard_event(
+ &mut self,
+ event: crossterm::KeyEvent,
+ ) -> std::result::Result<futures::Async<String>, Error> {
+ match event {
+ crossterm::KeyEvent::Char(c) => {
+ self.echo(c).map_err(|e| Error::IOError(e))?;
+
+ if c == '\n' {
+ return Ok(futures::Async::Ready(self.buffer.clone()));
+ }
+ self.buffer.push(c);
+ }
+ crossterm::KeyEvent::Ctrl(c) => {
+ if c == 'd' {
+ if self.buffer.is_empty() {
+ self.echo('\n').map_err(|e| Error::IOError(e))?;
+ return Err(Error::EOF);
+ }
+ }
+ if c == 'c' {
+ self.buffer = String::new();
+ self.echo('\n').map_err(|e| Error::IOError(e))?;
+ self.prompt().map_err(|e| Error::IOError(e))?;
+ }
+ }
+ _ => {}
+ }
+ return Ok(futures::Async::NotReady);
+ }
+
+ fn write(&self, buf: &[u8]) -> std::io::Result<()> {
+ let stdout = std::io::stdout();
+ let mut stdout = stdout.lock();
+ stdout.write(buf)?;
+ stdout.flush()
+ }
+
+ fn prompt(&self) -> std::io::Result<()> {
+ self.write(self.prompt.as_bytes())
+ }
+
+ fn echo(&self, c: char) -> std::io::Result<()> {
+ if c == '\n' {
+ self.write(b"\r\n")?;
+ return Ok(());
+ }
+
+ if !self.echo {
+ return Ok(());
+ }
+
+ let mut buf = [0u8; 4];
+ self.write(c.encode_utf8(&mut buf[..]).as_bytes())
+ }
+}
+
+impl futures::future::Future for Readline {
+ type Item = String;
+ type Error = Error;
+
+ fn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {
+ if !self.wrote_prompt {
+ self.prompt().map_err(|e| Error::IOError(e))?;
+ self.wrote_prompt = true;
+ }
+
+ let reader = self.reader.get_or_insert_with(|| {
+ KeyReader::new(tokio::prelude::task::current())
+ });
+ if let Some(event) = reader.poll() {
+ self.process_event(event)
+ } else {
+ Ok(futures::Async::NotReady)
+ }
+ }
+}
+
+pub fn readline(prompt: &str, echo: bool) -> Readline {
+ Readline {
+ reader: None,
+ buffer: String::new(),
+ prompt: prompt.to_string(),
+ wrote_prompt: false,
+ echo,
+ }
+}
+
+struct KeyReader {
+ events: std::sync::mpsc::Receiver<crossterm::InputEvent>,
+ quit: std::sync::mpsc::Sender<()>,
+}
+
+impl KeyReader {
+ fn new(task: tokio::prelude::task::Task) -> Self {
+ let reader = crossterm::input().read_sync();
+ let (events_tx, events_rx) = std::sync::mpsc::channel();
+ let (quit_tx, quit_rx) = std::sync::mpsc::channel();
+ std::thread::spawn(move || {
+ for event in reader {
+ let newline = event
+ == crossterm::InputEvent::Keyboard(
+ crossterm::KeyEvent::Char('\n'),
+ );
+ events_tx.send(event).unwrap();
+ task.notify();
+ if newline {
+ break;
+ }
+ if let Ok(_) = quit_rx.try_recv() {
+ break;
+ }
+ }
+ });
+
+ KeyReader {
+ events: events_rx,
+ quit: quit_tx,
+ }
+ }
+
+ fn poll(&self) -> Option<crossterm::InputEvent> {
+ if let Ok(event) = self.events.try_recv() {
+ return Some(event);
+ }
+ None
+ }
+}
+
+impl Drop for KeyReader {
+ fn drop(&mut self) {
+ // don't care if it fails to send, this can happen if the thread
+ // terminates due to seeing a newline before the keyreader goes out of
+ // scope
+ match self.quit.send(()) {
+ _ => {}
+ }
+ }
+}
diff --git a/src/repl.rs b/src/repl.rs
new file mode 100644
index 0000000..c44174a
--- /dev/null
+++ b/src/repl.rs
@@ -0,0 +1,53 @@
+use futures::future::Future;
+use std::io::Write;
+
+#[derive(Debug)]
+enum Error {
+ ReadError(crate::readline::Error),
+ // EvalError(std::io::Error),
+ PrintError(std::io::Error),
+ // LoopError,
+}
+
+pub fn repl() {
+ tokio::run(tokio::prelude::future::lazy(|| {
+ let mut done = false;
+ while !done {
+ let res = read()
+ .and_then(move |line| eval(&line))
+ .and_then(move |out| print(&out))
+ .wait();
+ match res {
+ Ok(_) => {}
+ Err(Error::ReadError(crate::readline::Error::EOF)) => {
+ done = true;
+ }
+ Err(e) => {
+ let stderr = std::io::stderr();
+ let mut stderr = stderr.lock();
+ write!(stderr, "error: {:?}", e).unwrap();
+ stderr.flush().unwrap();
+ done = true;
+ }
+ }
+ }
+ futures::future::ok(())
+ }));
+}
+
+fn read() -> impl futures::future::Future<Item = String, Error = Error> {
+ crate::readline::readline("$ ", true).map_err(|e| Error::ReadError(e))
+}
+
+fn eval(line: &str) -> Result<String, Error> {
+ Ok(format!("got line '{}'\r\n", line))
+}
+
+fn print(out: &str) -> Result<(), Error> {
+ let stdout = std::io::stdout();
+ let mut stdout = stdout.lock();
+ stdout
+ .write(out.as_bytes())
+ .map_err(|e| Error::PrintError(e))?;
+ stdout.flush().map_err(|e| Error::PrintError(e))
+}