console-rs / indicatif

A command line progress reporting library for Rust
MIT License
4.22k stars 238 forks source link

Pressing key during progress bar causes rerender #587

Closed danman113 closed 9 months ago

danman113 commented 9 months ago

When spawning a progress bar like so:

for _ in 0..miles_to_travel {
            thread::sleep(Duration::from_millis(60));
            miles_traveled += 1;
            progress.set_position(((miles_traveled as f32 / miles_to_travel as f32) * 100.0) as u64);
        }

If I press any key during the progress bar, I get a rerender of the progress bar.

███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 7/100
██████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 12/100
███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 14/100
████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 16/10

How do I prevent this?

chris-laplante commented 9 months ago

Unless you go with something like ncurses/curses, I don't think there is a way to prevent this. It's kind of just a fact of life for terminal UIs. Try pressing enter during a git clone for instance. It will mess up the output too.

danman113 commented 9 months ago

Try pressing enter during a git clone for instance. It will mess up the output too.

Yup, I tried this for git clone, cargo build --release, npm install, they all behave the same.

Unless you go with something like ncurses/curses, I don't think there is a way to prevent this.

Yeah I think my usecase (game) might actually be better met with either a more low level terminal library (crossterm) or a full fledged TUI framework (ratatui).

That said, while I don't see an easy way to fix this at the level of abstraction indicatif and console operate at, I think it's definitely something that can be fixed. The solution is basically to disregard all terminal inputs while the progress bar is updated, or at least provide a way for users to selectively disregard terminal inputs. I think this is what crossterm::enable_raw_mode()? essentially does.

I was able to get it to work somewhat how I want by constantly calling term.read_key() in a separate thread while I update the progress bar.

let (sx, rx) = mpsc::channel::<()>();
let thread_term = term.clone();
let handle = thread::spawn(move || loop {
    if rx.try_recv().is_ok() {
        break;
    }
    thread_term.read_key().unwrap();
});
for _ in 0..miles_to_travel {
    thread::sleep(Duration::from_millis(60));
    miles_traveled += 1;
    progress.set_position(((miles_traveled as f32 / miles_to_travel as f32) * 100.0) as u64);
}
progress.finish();
term.write_line("Done!").unwrap();
sx.send(()).unwrap();
handle.join().unwrap();

This has the side effect that you must press a key to continue after the progress bar is done, but for my usecase that's fine. Also I can see how this might be a problem if you wanted to, say prompt the user for input in the middle of a suspend call.

Anyways you can close this ticket, but please keep this feature request in mind if you ever plan on doing a major refactor.