kkawakam / rustyline

Readline Implementation in Rust
https://crates.io/crates/rustyline/
MIT License
1.52k stars 175 forks source link

Multiple lines sent on pty input by `expect` are not seen by process #690

Closed mmastrac closed 6 months ago

mmastrac commented 1 year ago

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
gwenn commented 1 year ago

I will try to reproduce. Would you mind trying this setting: https://docs.rs/rustyline/latest/rustyline/config/enum.Behavior.html#variant.PreferTerm ?

    let config = Config::builder()
        .behavior(Behavior::PreferTerm)
        .build();
    let mut rl = DefaultEditor::with_config(config)?;
gwenn commented 1 year ago

Ok, I can reproduce. Liner seems OK. But not linenoise, replxx, ... I tried with env_logger::init(); and export RUST_LOG=rustyline=debug but that didn't help. It may be caused by the way we activate raw mode with TCSADRAIN here ? I will try to investigate further...

gwenn commented 1 year ago

Ok, rustyline uses a BufReader. And the whole input "1\n2\n" is read at once but only "1\n" is treated ("2\n" is discarded).

diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index 23bfcce..70e02f4 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -144,6 +144,7 @@ impl Read for TtyIn {
                     return Err(error);
                 }
             } else {
+                debug!(target: "rustyline", "read: {:?}", &buf[..res as usize]);
                 #[allow(clippy::cast_sign_loss)]
                 return Ok(res as usize);
             }
[DEBUG rustyline] read: [49, 10, 50, 10]

The BufReader is used for bracketed paste. See https://en.wikipedia.org/wiki/Bracketed-paste Maybe we should set the buffer size to 1 when bracketed paste is disabled ?

gwenn commented 1 year ago

With the following patch, I manage to fix the issue:

diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index 23bfcce..ff96dd6 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -229,8 +229,13 @@ impl PosixRawReader {
         key_map: PosixKeyMap,
         pipe_reader: Option<PipeReader>,
     ) -> Self {
+        let buffer_size = if config.enable_bracketed_paste() {
+            1024
+        } else {
+            1
+        };
         Self {
-            tty_in: BufReader::with_capacity(1024, TtyIn { fd, sigwinch_pipe }),
+            tty_in: BufReader::with_capacity(buffer_size, TtyIn { fd, sigwinch_pipe }),
             timeout_ms: config.keyseq_timeout(),
             parser: Parser::new(),
             key_map,

Or maybe the expect file could be like this:

spawn cargo run --example numeric_input
send "1\n"
expect "1"
spawn cargo run --example numeric_input
send "2\n"
expect "2"

?

cosmikwolf commented 12 months ago

I think I am running into this issue when I pipe the output of a command with multiple lines into my app that uses rustyline,

here is a simulation of my example, I can create a code example if needed.

echo "test\ninput" | cargo run rustylineapp

Should produce:

Result: test
input

but it produces

Result: test
Result: input
gwenn commented 11 months ago

@cosmikwolf For me, this is the expected behaviour when stdin is not a tty (here a pipe). expect creates a pty.

liner % echo "test\ninput" | target/debug/examples/comments history
History file: history
[prompt]
% test
[prompt]
% nput
[prompt]
% exiting...
linenoise % echo "test\ninput" | ./linenoise_example
echo: 'test'
echo: 'input'
replxx % echo "test\ninput" | ./build/replxx-example-c-api
starting...
thanks for the input: test
thanks for the input: input

Exiting Replxx
isocline % echo "test\ninput" | ./example
...
-----
est
-----
-----
input
-----
done
gwenn commented 7 months ago

https://github.com/prompt-toolkit/python-prompt-toolkit/blob/0aa8a9762a51289e753a6e311edcb40b65224c66/src/prompt_toolkit/input/typeahead.py

To support type ahead, this module will store all the key strokes that were read too early, so that they can be feed into to the next prompt() call

gwenn commented 7 months ago

@mmastrac could you please try #761 ?

gwenn commented 5 months ago

Version 14.0.0 released