Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cursor position swallowed by input reader #199

Closed
sophiajt opened this issue Sep 6, 2019 · 9 comments · Fixed by crossterm-rs/crossterm-cursor#10 or crossterm-rs/crossterm-input#9

Comments

@sophiajt
Copy link
Contributor

sophiajt commented Sep 6, 2019

This example will fail back to the terminal or hang after hitting enter 1-5 times (on Linux):

use crossterm::{cursor, input, InputEvent, KeyEvent, RawScreen};
use std::io::Write;

fn main() {
    let screen = RawScreen::into_raw_mode();
    let mut input = input();

    let mut stdin = input.read_async();
    let mut cursor = cursor();

    print!("> ");
    let _ = std::io::stdout().flush();

    let mut reset_position = cursor.pos();
    let mut buffer = String::new();

    loop {
        if let Some(key) = stdin.next() {
            match key {
                InputEvent::Keyboard(event) => match event {
                    KeyEvent::Esc => {
                        break;
                    }
                    KeyEvent::Char(c) if c == '\n' => {
                        print!("\r\n> ");
                        let _ = std::io::stdout().flush();

                        reset_position = cursor.pos();
                        buffer = String::new();
                    }
                    _ => {}
                },
                _ => {}
            }
        }
    }
}
@TimonPost
Copy link
Member

Hi, what version are you using? This is an issue that had been solved some time ago.

@sophiajt
Copy link
Contributor Author

sophiajt commented Sep 9, 2019

@TimonPost - I believe this is from the latest version released on crates.io

@zrzka
Copy link
Contributor

zrzka commented Sep 20, 2019

I rewrote this example for the latest master (& #239 included):

use std::io::Write;

use crossterm::{cursor, input, InputEvent, KeyEvent, RawScreen, Result};

fn main() -> Result<()> {
    let _screen = RawScreen::into_raw_mode();
    let input = input();

    let mut stdin = input.read_async();
    let cursor = cursor();

    print!("> ");
    let _ = std::io::stdout().flush()?;

    let mut _reset_position = cursor.pos()?;

    loop {
        if let Some(key) = stdin.next() {
            match key {
                InputEvent::Keyboard(KeyEvent::Esc) => break,
                InputEvent::Keyboard(KeyEvent::Enter) => {
                    print!("\r\n> ");
                    let _ = std::io::stdout().flush();
                    _reset_position = cursor.pos()?;
                }
                _ => {}
            }
        }
    }

    Ok(())
}

Didn't debug it yet, but the described symptoms are same on macOS. This code:

  • Immediately exits with 0 exit code
    • When I print the input event, it immediately receives KeyEvent::Esc
    • It receives it even couple of times in a row and it behaves in this way
      • Esc received -> quit, run again, Esc received -> quit, run again, Esc received, run again and hang
  • Or it hangs immediately and I have to kill it

@zrzka
Copy link
Contributor

zrzka commented Sep 20, 2019

And I can confirm that it behaves correctly when I comment out both cursor.pos()?; lines.

@zrzka
Copy link
Contributor

zrzka commented Sep 20, 2019

Backtrace ...

* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x00007fff7d54eef2 libsystem_kernel.dylib`read + 10
    frame #1: 0x000000010c435125 issue199`_$LT$std..io..buffered..BufReader$LT$R$GT$$u20$as$u20$std..io..BufRead$GT$::fill_buf::hafe10937c7a8b8c4 [inlined] std::sys::unix::fd::FileDesc::read::h0aa37bdb84297f4d at fd.rs:49:12 [opt]
    frame #2: 0x000000010c43510f issue199`_$LT$std..io..buffered..BufReader$LT$R$GT$$u20$as$u20$std..io..BufRead$GT$::fill_buf::hafe10937c7a8b8c4 [inlined] _$LT$std..sys..unix..stdio..Stdin$u20$as$u20$std..io..Read$GT$::read::h7b8b310803c46534 at stdio.rs:15 [opt]
    frame #3: 0x000000010c43510f issue199`_$LT$std..io..buffered..BufReader$LT$R$GT$$u20$as$u20$std..io..BufRead$GT$::fill_buf::hafe10937c7a8b8c4 [inlined] _$LT$std..io..stdio..StdinRaw$u20$as$u20$std..io..Read$GT$::read::h7b8392b5170b7eba at stdio.rs:76 [opt]
    frame #4: 0x000000010c43510f issue199`_$LT$std..io..buffered..BufReader$LT$R$GT$$u20$as$u20$std..io..BufRead$GT$::fill_buf::hafe10937c7a8b8c4 [inlined] _$LT$std..io..stdio..Maybe$LT$R$GT$$u20$as$u20$std..io..Read$GT$::read::hffe6d2184cc6c02a at stdio.rs:138 [opt]
    frame #5: 0x000000010c43510f issue199`_$LT$std..io..buffered..BufReader$LT$R$GT$$u20$as$u20$std..io..BufRead$GT$::fill_buf::hafe10937c7a8b8c4 at buffered.rs:281 [opt]
    frame #6: 0x000000010c436275 issue199`_$LT$std..io..stdio..StdinLock$u20$as$u20$std..io..BufRead$GT$::fill_buf::hf4ae9ad16bb865ae at stdio.rs:373:50 [opt]
    frame #7: 0x000000010c42f32d issue199`std::io::read_until::hba89bf66fe83a89e(r=&0x7ffee37fb340, delim='[', buf=&0x7ffee37fb350) at mod.rs:1485:34
    frame #8: 0x000000010c42cc3f issue199`std::io::BufRead::read_until::hed958eb2deeefb3c(self=&0x7ffee37fb340, byte='[', buf=&0x7ffee37fb350) at mod.rs:1680:8
    frame #9: 0x000000010c42b519 issue199`crossterm_cursor::sys::unix::pos_raw::h5dc4ed1acc1f67a5 at unix.rs:45:4
    frame #10: 0x000000010c42ad93 issue199`crossterm_cursor::sys::unix::get_cursor_position::hc6ca5bff904aba00 at unix.rs:12:8
    frame #11: 0x000000010c428528 issue199`_$LT$crossterm_cursor..cursor..ansi_cursor..AnsiCursor$u20$as$u20$crossterm_cursor..cursor..ITerminalCursor$GT$::pos::heabe58a0595d05b9(self=&0x7ffee37fb990) at ansi_cursor.rs:54:8
    frame #12: 0x000000010c42d8fc issue199`crossterm_cursor::cursor::cursor::TerminalCursor::pos::hff90162fe640f5a6(self=&0x7ffee37fb990) at cursor.rs:63:8
    frame #13: 0x000000010c404491 issue199`issue199::main::h37a14e39ff0393f5 at issue199.rs:15:30
    frame #14: 0x000000010c4053a1 issue199`std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h1e19ed67ef18c803 at rt.rs:64:33
    frame #15: 0x000000010c43be28 issue199`std::panicking::try::do_call::ha6f6dadf842d3c9c [inlined] std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::ha9fcfb3b278bcf3f at rt.rs:49:12 [opt]
    frame #16: 0x000000010c43be1c issue199`std::panicking::try::do_call::ha6f6dadf842d3c9c at panicking.rs:296 [opt]
    frame #17: 0x000000010c43dfbf issue199`__rust_maybe_catch_panic at lib.rs:82:7 [opt]
    frame #18: 0x000000010c43c84e issue199`std::rt::lang_start_internal::hc1ac2c20e9f8edf2 [inlined] std::panicking::try::h3bc5f919887df4c4 at panicking.rs:275:12 [opt]
    frame #19: 0x000000010c43c81b issue199`std::rt::lang_start_internal::hc1ac2c20e9f8edf2 [inlined] std::panic::catch_unwind::hd36fe659e375f196 at panic.rs:394 [opt]
    frame #20: 0x000000010c43c81b issue199`std::rt::lang_start_internal::hc1ac2c20e9f8edf2 at rt.rs:48 [opt]
    frame #21: 0x000000010c405372 issue199`std::rt::lang_start::hae85193284087024(main=&0x10c404250, argc=1, argv=&0x7ffee37fbec8) at rt.rs:64:4
    frame #22: 0x000000010c404b72 issue199`main + 34
    frame #23: 0x00007fff7d4183d5 libdyld.dylib`start + 1
    frame #24: 0x00007fff7d4183d5 libdyld.dylib`start + 1

Hang caused by this line:

https://github.com/TimonPost/crossterm/blob/4952cb33d9d4e1ff8a32073597d28148274e39b6/crossterm_cursor/src/sys/unix.rs#L45

Seems like it's waiting for the b'[' and never receives it.

@TimonPost does it ring a bell? You said you believe it was fixed, maybe you know what's going on from top of your head.

@zrzka
Copy link
Contributor

zrzka commented Sep 20, 2019

Seems like a racing problem. We ask for the cursor position with \x1B[6n, reply is \x1B[123;123R, but the reply reaches the async reader as well (\x1B = Esc). Checking the code and I'm unable to find any locking mechanism to avoid this. This probably explains why it sometimes quits immediately, why it hangs (b'[' consumed already by the async reader), ... Also the UNIX AsyncReader reads from the /dev/tty directly, but pos_raw() does use std::io::Stdin. Have to check the behavior when both ways are used. I'm guessing here, commenting what I see in the code, it needs more testing.

@zrzka
Copy link
Contributor

zrzka commented Sep 20, 2019

Here's the input I've got (in this exact order):

read_async().next():    27 Esc
read_async().next():    91 [
pos_raw():              51 3
pos_raw():              48 0
pos_raw():              59 ;
pos_raw():              51 3
pos_raw():              82 R
read_async().next():    13 Enter

When the example quits immediately:

read_async().next():    27 Esc
pos_raw():              91 [
pos_raw():              52 4
pos_raw():              59 ;
pos_raw():              51 3
pos_raw():              82 R

When the example is waiting for the enter, I hit it and the it ends:

read_async().next():    27 Esc
pos_raw():              91 [
pos_raw():              51 3
pos_raw():              48 0
pos_raw():              59 ;
pos_raw():              51 3
pos_raw():              82 R
read_async().next():    13 Enter
read_async().next():    27 Esc
pos_raw():              91 [
pos_raw():              51 3
pos_raw():              48 0
pos_raw():              59 ;
pos_raw():              51 3
pos_raw():              82 R

As you can see, the sequence Esc[ is sometimes consumed by the AsyncReader, sometimes by the pos_raw(), sometimes by both (one consumes Esc, another one [), ...

This must be synced somehow.

@TimonPost
Copy link
Member

I have no direct answer for that. This topic has come up in the past checkout :
#140

@TimonPost TimonPost changed the title cursor pos() fails when used with read_async() Cursor position swallowed by input reader Sep 23, 2019
@zrzka
Copy link
Contributor

zrzka commented Sep 26, 2019

Another comment from @MaulingMonkey:

https://github.com/crossterm-rs/crossterm-cursor/blob/13f6c0878ac871750c5893689e4743ac8084c13b/src/sys/unix.rs#L45

This has a few problems:

  1. This can discard real user input
  2. This will likely hang if stdin was redirected, since it's not reading from the terminal associated with stdout

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants