Repository: kkawakam/rustyline (rust)

Issue:
Multiple lines sent on pty input by `expect` are not seen by process
When sending input via `expect`, `rustyline` doesn't appear to process anything but the first line.

`example.expect`

```
spawn cargo run --example numeric_input
send "1\n2\n"
expect "123"
```

Run `expect example.expect`

```
spawn cargo run --example numeric_input
1
2
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/examples/numeric_input`
> 1
Num: 1
> ^C✘-INT ~/Documents/github/rustyline [master|✔] 
```

Expected:

Output should be `Num 1` followed by `Num 2`.

Actual:

Output is just `Num 1`

Workaround:

It _does_ work without using `expect`, however:

```
$ echo -e "1\n2" | cargo run --example numeric_input
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/examples/numeric_input`
Num: 1
Num: 2
Error: Eof
```

Pull request:
Fix typeahead
Fix #690

To test, you need to activate the feature like this:
```diff
diff --git a/Cargo.toml b/Cargo.toml
index 7ac2645..fd27bb7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -87,7 +87,7 @@ name = "input_validation"
 required-features = ["derive"]
 [[example]]
 name = "numeric_input"
-required-features = ["custom-bindings"]
+required-features = ["custom-bindings", "buffer-redux"]
 [[example]]
 name = "read_password"
 required-features = ["derive"]
```

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 2f03621..58b99eb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -47,6 +47,7 @@ utf8parse = "0.2"
 skim = { version = "0.10", optional = true, default-features = false }
 signal-hook = { version = "0.3", optional = true, default-features = false }
 termios = { version = "0.3.3", optional = true }
+buffer-redux = { version = "1.0", optional = true, default-features = false }
 
 [target.'cfg(windows)'.dependencies]
 windows-sys = { version = "0.52.0", features = ["Win32_Foundation", "Win32_System_Console", "Win32_Security", "Win32_System_Threading", "Win32_UI_Input_KeyboardAndMouse"] }
diff --git a/src/binding.rs b/src/binding.rs
index edb9afb..7c08383 100644
--- a/src/binding.rs
+++ b/src/binding.rs
@@ -250,6 +250,7 @@ mod test {
 
     #[test]
     #[ignore]
+    #[cfg(target_arch = "x86_64")]
     fn size_of_event() {
         use core::mem::size_of;
         assert_eq!(size_of::<Event>(), 32);
diff --git a/src/lib.rs b/src/lib.rs
index 972af0f..fcea11d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -51,7 +51,7 @@ use log::debug;
 pub use rustyline_derive::{Completer, Helper, Highlighter, Hinter, Validator};
 use unicode_width::UnicodeWidthStr;
 
-use crate::tty::{RawMode, RawReader, Renderer, Term, Terminal};
+use crate::tty::{Buffer, RawMode, RawReader, Renderer, Term, Terminal};
 
 #[cfg(feature = "custom-bindings")]
 pub use crate::binding::{ConditionalEventHandler, Event, EventContext, EventHandler};
@@ -586,6 +586,7 @@ impl<'h> Context<'h> {
 #[must_use]
 pub struct Editor<H: Helper, I: History> {
     term: Terminal,
+    buffer: Option<Buffer>,
     history: I,
     helper: Option<H>,
     kill_ring: KillRing,
@@ -621,6 +622,7 @@ impl<H: Helper, I: History> Editor<H, I> {
         )?;
         Ok(Self {
             term,
+            buffer: None,
             history,
             helper: None,
             kill_ring: KillRing::new(60),
@@ -704,7 +706,9 @@ impl<H: Helper, I: History> Editor<H, I> {
             );
         }
 
-        let mut rdr = self.term.create_reader(&self.config, term_key_map);
+        let mut rdr = self
+            .term
+            .create_reader(self.buffer.take(), &self.config, term_key_map);
         if self.term.is_output_tty() && self.config.check_cursor_position() {
             if let Err(e) = s.move_cursor_at_leftmost(&mut rdr) {
                 if let ReadlineError::WindowResized = e {
@@ -794,6 +798,7 @@ impl<H: Helper, I: History> Editor<H, I> {
         if cfg!(windows) {
             let _ = original_mode; // silent warning
         }
+        self.buffer = rdr.unbuffer();
         Ok(s.line.into_string())
     }
 
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index e79d2a9..8ac335d 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -23,6 +23,7 @@ pub enum Event {
 
 /// Translate bytes read from stdin to keys.
 pub trait RawReader {
+    type Buffer;
     /// Blocking wait for either a key press or an external print
     fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event>; // TODO replace calls to `next_key` by `wait_for_input` where relevant
     /// Blocking read of key pressed.
@@ -34,6 +35,8 @@ pub trait RawReader {
     fn read_pasted_text(&mut self) -> Result<String>;
     /// Check if `key` is bound to a peculiar command
     fn find_binding(&self, key: &KeyEvent) -> Option<Cmd>;
+    /// Backup type ahead
+    fn unbuffer(self) -> Option<Buffer>;
 }
 
 /// Display prompt, line and cursor in terminal output
@@ -215,8 +218,9 @@ pub trait ExternalPrinter {
 
 /// Terminal contract
 pub trait Term {
+    type Buffer;
     type KeyMap;
-    type Reader: RawReader; // rl_instream
+    type Reader: RawReader<Buffer = Self::Buffer>; // rl_instream
     type Writer: Renderer<Reader = Self::Reader>; // rl_outstream
     type Mode: RawMode;
     type ExternalPrinter: ExternalPrinter;
@@ -241,7 +245,12 @@ pub trait Term {
     /// Enable RAW mode for the terminal.
     fn enable_raw_mode(&mut self) -> Result<(Self::Mode, Self::KeyMap)>;
     /// Create a RAW reader
-    fn create_reader(&self, config: &Config, key_map: Self::KeyMap) -> Self::Reader;
+    fn create_reader(
+        &self,
+        buffer: Option<Self::Buffer>,
+        config: &Config,
+        key_map: Self::KeyMap,
+    ) -> Self::Reader;
     /// Create a writer
     fn create_writer(&self) -> Self::Writer;
     fn writeln(&self) -> Result<()>;
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index 75dace1..700e46f 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -1,8 +1,12 @@
 //! Unix specific definitions
+#[cfg(feature = "buffer-redux")]
+use buffer_redux::BufReader;
 use std::cmp;
 use std::collections::HashMap;
 use std::fs::{File, OpenOptions};
-use std::io::{self, BufReader, ErrorKind, Read, Write};
+#[cfg(not(feature = "buffer-redux"))]
+use std::io::BufReader;
+use std::io::{self, ErrorKind, Read, Write};
 use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, RawFd};
 use std::os::unix::net::UnixStream;
 use std::sync::atomic::{AtomicBool, Ordering};
@@ -91,6 +95,13 @@ fn is_a_tty(fd: RawFd) -> bool {
     isatty(fd).unwrap_or(false)
 }
 
+#[cfg(any(not(feature = "buffer-redux"), test))]
+pub type PosixBuffer = ();
+#[cfg(all(feature = "buffer-redux", not(test)))]
+pub type PosixBuffer = buffer_redux::Buffer;
+#[cfg(not(test))]
+pub type Buffer = PosixBuffer;
+
 pub type PosixKeyMap = HashMap<KeyEvent, Cmd>;
 #[cfg(not(test))]
 pub type KeyMap = PosixKeyMap;
@@ -228,12 +239,22 @@ impl PosixRawReader {
     fn new(
         fd: RawFd,
         sigwinch_pipe: Option<RawFd>,
+        buffer: Option<PosixBuffer>,
         config: &Config,
         key_map: PosixKeyMap,
         pipe_reader: Option<PipeReader>,
     ) -> Self {
+        let inner = TtyIn { fd, sigwinch_pipe };
+        #[cfg(any(not(feature = "buffer-redux"), test))]
+        let (tty_in, _) = (BufReader::with_capacity(1024, inner), buffer);
+        #[cfg(all(feature = "buffer-redux", not(test)))]
+        let tty_in = if let Some(buffer) = buffer {
+            BufReader::with_buffer(buffer, inner)
+        } else {
+            BufReader::with_capacity(1024, inner)
+        };
         Self {
-            tty_in: BufReader::with_capacity(1024, TtyIn { fd, sigwinch_pipe }),
+            tty_in,
             timeout_ms: config.keyseq_timeout(),
             parser: Parser::new(),
             key_map,
@@ -750,6 +771,8 @@ impl PosixRawReader {
 }
 
 impl RawReader for PosixRawReader {
+    type Buffer = PosixBuffer;
+
     #[cfg(not(feature = "signal-hook"))]
     fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
         match self.pipe_reader {
@@ -840,6 +863,17 @@ impl RawReader for PosixRawReader {
         }
         cmd
     }
+
+    #[cfg(any(not(feature = "buffer-redux"), test))]
+    fn unbuffer(self) -> Option<PosixBuffer> {
+        None
+    }
+
+    #[cfg(all(feature = "buffer-redux", not(test)))]
+    fn unbuffer(self) -> Option<PosixBuffer> {
+        let (_, buffer) = self.tty_in.into_inner_with_buffer();
+        Some(buffer)
+    }
 }
 
 impl Receiver for Utf8 {
@@ -1253,6 +1287,7 @@ impl PosixTerminal {
 }
 
 impl Term for PosixTerminal {
+    type Buffer = PosixBuffer;
     type CursorGuard = PosixCursorGuard;
     type ExternalPrinter = ExternalPrinter;
     type KeyMap = PosixKeyMap;
@@ -1371,10 +1406,16 @@ impl Term for PosixTerminal {
     }
 
     /// Create a RAW reader
-    fn create_reader(&self, config: &Config, key_map: PosixKeyMap) -> PosixRawReader {
+    fn create_reader(
+        &self,
+        buffer: Option<PosixBuffer>,
+        config: &Config,
+        key_map: PosixKeyMap,
+    ) -> PosixRawReader {
         PosixRawReader::new(
             self.tty_in,
             self.sigwinch.as_ref().map(|s| s.pipe),
+            buffer,
             config,
             key_map,
             self.pipe_reader.clone(),
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index f1598cb..3ce4169 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -69,6 +69,10 @@ fn get_console_mode(handle: HANDLE) -> Result<console::CONSOLE_MODE> {
     Ok(original_mode)
 }
 
+type ConsoleBuffer = ();
+#[cfg(not(test))]
+pub type Buffer = ConsoleBuffer;
+
 type ConsoleKeyMap = ();
 #[cfg(not(test))]
 pub type KeyMap = ConsoleKeyMap;
@@ -141,6 +145,8 @@ impl ConsoleRawReader {
 }
 
 impl RawReader for ConsoleRawReader {
+    type Buffer = ConsoleBuffer;
+
     fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
         match self.pipe_reader {
             Some(_) => self.select(),
@@ -159,6 +165,10 @@ impl RawReader for ConsoleRawReader {
     fn find_binding(&self, _: &KeyEvent) -> Option<Cmd> {
         None
     }
+
+    fn unbuffer(self) -> Option<ConsoleBuffer> {
+        None
+    }
 }
 
 fn read_input(handle: HANDLE, max_count: u32) -> Result<KeyEvent> {
@@ -640,6 +650,7 @@ impl Console {
 }
 
 impl Term for Console {
+    type Buffer = ConsoleBuffer;
     type CursorGuard = ConsoleCursorGuard;
     type ExternalPrinter = ExternalPrinter;
     type KeyMap = ConsoleKeyMap;
@@ -801,7 +812,12 @@ impl Term for Console {
         ))
     }
 
-    fn create_reader(&self, _: &Config, _: ConsoleKeyMap) -> ConsoleRawReader {
+    fn create_reader(
+        &self,
+        _: Option<ConsoleBuffer>,
+        _: &Config,
+        _: ConsoleKeyMap,
+    ) -> ConsoleRawReader {
         ConsoleRawReader::create(self.conin, self.pipe_reader.clone())
     }
 


Test patch:
diff --git a/src/tty/test.rs b/src/tty/test.rs
index d26af5e..755e523 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -12,6 +12,7 @@ use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
 use crate::{Cmd, Result};
 
+pub type Buffer = ();
 pub type KeyMap = ();
 pub type Mode = ();
 
@@ -22,6 +23,8 @@ impl RawMode for Mode {
 }
 
 impl<'a> RawReader for Iter<'a, KeyEvent> {
+    type Buffer = Buffer;
+
     fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
         self.next_key(single_esc_abort).map(Event::KeyPress)
     }
@@ -45,9 +48,15 @@ impl<'a> RawReader for Iter<'a, KeyEvent> {
     fn find_binding(&self, _: &KeyEvent) -> Option<Cmd> {
         None
     }
+
+    fn unbuffer(self) -> Option<Buffer> {
+        None
+    }
 }
 
 impl RawReader for IntoIter<KeyEvent> {
+    type Buffer = Buffer;
+
     fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
         self.next_key(single_esc_abort).map(Event::KeyPress)
     }
@@ -76,6 +85,10 @@ impl RawReader for IntoIter<KeyEvent> {
     fn find_binding(&self, _: &KeyEvent) -> Option<Cmd> {
         None
     }
+
+    fn unbuffer(self) -> Option<Buffer> {
+        None
+    }
 }
 
 #[derive(Default)]
@@ -160,6 +173,7 @@ pub struct DummyTerminal {
 }
 
 impl Term for DummyTerminal {
+    type Buffer = Buffer;
     type CursorGuard = ();
     type ExternalPrinter = DummyExternalPrinter;
     type KeyMap = KeyMap;
@@ -208,7 +222,7 @@ impl Term for DummyTerminal {
         Ok(((), ()))
     }
 
-    fn create_reader(&self, _: &Config, _: KeyMap) -> Self::Reader {
+    fn create_reader(&self, _: Option<Buffer>, _: &Config, _: KeyMap) -> Self::Reader {
         self.keys.clone().into_iter()
     }