Description
Describe the bug
When using the termion back-end and then reading from stdin, up to two characters are dropped.
To Reproduce
Simple modification of the pause example at https://github.com/gyscos/cursive/blob/main/examples/src/bin/pause.rs:
fn main() {
let mut siv = cursive::termion();
siv.add_layer(
cursive::views::Dialog::text("Please write your message.")
.button("Ok", |s| s.quit()),
);
siv.run();
println!("Enter your message here:");
let mut line = String::new();
std::io::stdin().read_line(&mut line).unwrap();
println!("Your message was: {:?}", line);
}
Expected behavior:
Enter your message here:
hello
Your message was: "hello\n"
Actual behavior:
Enter your message here:
hello
Your message was: "llo\n"
Note that the first two characters of the provided message have been dropped.
Environment
- Operating system used: macOS 10.15.7
- Backend used:
termion
- Current locale:
LANG="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_CTYPE="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_ALL=
- Cursive version: 0.16.3
Analysis
I believe the main problem is this loop in combination with the implementation of termion:
cursive/cursive/src/backends/termion.rs
Lines 93 to 108 in 7cf597f
When you ask termion for the next event, it reads up to two bytes, which is why specifically up to two characters are dropped:
Even after the Cursive
instance is dropped, the input thread is still running in the background. Once data has been written to stdin, this thread will then read up to two bytes and attempt to run input_sender.send(event)
, which will fail and then cause the thread to terminate. The calling code may then attempt to read from stdin, but the bytes are already gone.
The ideal fix would be to terminate the input thread once the Cursive instance has been dropped, but I can't figure out a good way to do that, since the call to events.next()
is fundamentally blocking. It might be possible to use a variant of select
to poll the input to see if it has data before calling events.next()
, but that seems complicated and non-portable.
I haven't been able to figure out a good workaround for user code. In particular, I expected that creating the Termion back-end with /dev/tty
would fix the issue, but it doesn't seem to.
Termion has an item called termion::async_stdin
, which might be relevant towards fixing this issue.