console-rs / indicatif

A command line progress reporting library for Rust
MIT License
4.23k stars 240 forks source link

If the number of progress bar exceed terminal height, newlines appears instead of clearing itself. #496

Closed pombadev closed 11 months ago

pombadev commented 1 year ago

Similar to #144 , if progress bars exceeds terminal's height i.e if there are more progress bars than it is able fit in visible at once, newlines are created. Fix provided in #144 doesn't help either.

Reproduction steps:

indicatif = "0.17.2"
use std::thread;
use std::time::Duration;

use indicatif::{MultiProgress, ProgressBar, ProgressStyle};

fn main() {
    let m = MultiProgress::new();
    let sty = ProgressStyle::with_template(
        "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
    )
    .unwrap()
    .progress_chars("##-");

    m.println("starting!").unwrap();

    let mut h = vec![];

    for _ in 0..50 {
        let pb = m.add(ProgressBar::new(128));
        pb.set_style(sty.clone());

        let m_clone = m.clone();
        let h1 = thread::spawn(move || {
            for i in 0..128 {
                pb.set_message(format!("item #{}", i + 1));
                pb.inc(1);
                thread::sleep(Duration::from_millis(50));
            }
            m_clone.println("pb1 is done!").unwrap();
            pb.finish_with_message("done");
        });

        h.push(h1);
    }

    for k in h {
        k.join().unwrap();
    }

    // m.clear().unwrap();
}

Use this code and run it

Example

djc commented 1 year ago

@chris-laplante any ideas?

chris-laplante commented 1 year ago

@chris-laplante any ideas?

Yes, I think we can handle this like how tqdm handles it: https://github.com/tqdm/tqdm/issues/918. Basically, just like how we autodetect terminal width we should also look at terminal height and only try to draw as many progress bars as will fit on the screen.

peschu123 commented 1 year ago

I was playing around with async and I think I have the same problem with the code below.

It looks quite a bit more weird (in the error case). Also I use multiple progress bars for tasks and one main progress bar at the bottom to track overall progress.

Just change the "ITEMS" variable to a lower or higher value.

use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use rand::prelude::*;
use std::convert::TryInto;
use tokio::task::JoinSet;
use tokio::time::{sleep, Duration};
use uuid::Uuid;

#[tokio::main]
async fn main() {
    const ITEMS: usize = 30;
    let uuids: Vec<(Uuid, usize)> = (0..ITEMS)
        .into_iter()
        // generate new uuid and an index
        .map(|x| (Uuid::new_v4(), x))
        .collect();

    let multi_pb = MultiProgress::new();
    let items_u64: u64 = ITEMS.try_into().unwrap();
    let main_pb = multi_pb.add(ProgressBar::new(items_u64));
    main_pb.set_style(
        ProgressStyle::with_template(
            "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
        )
        .unwrap()
        .progress_chars("##-"),
    );

    main_pb.set_message("total  ");
    // Make the main progress bar render immediately rather than waiting for the
    // first task to finish.
    main_pb.tick();

    let mut set = JoinSet::new();
    for (uuid, index) in uuids {
        let steps = 100;
        let task_pb = multi_pb.insert_before(&main_pb, ProgressBar::new(steps));
        task_pb.set_style(
            ProgressStyle::with_template(
                "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
            )
            .unwrap()
            .progress_chars("##-"),
        );
        set.spawn(do_stuff(uuid, index, steps, task_pb));
    }

    let mut seen = [false; ITEMS];
    while let Some(res) = set.join_next().await {
        let idx = res.unwrap();
        seen[idx] = true;
        main_pb.inc(1);
    }
    main_pb.finish();
}

async fn do_stuff(uuid: Uuid, index: usize, steps: u64, task_pb: ProgressBar) -> usize {
    let msg = format!("app #{} with id:{}", index, uuid);
    task_pb.set_message(msg.clone());

    // calculate "tick size" for progress bar to use with sleep (millisecs / steps)
    let num = rand::thread_rng().gen_range(steps..=10000);
    let tick = num / steps;

    for _ in 0..steps {
        sleep(Duration::from_millis(tick)).await;
        task_pb.inc(1);
    }
    let msg = format!("DONE {}", &msg);
    task_pb.finish_with_message(msg);
    index
}