← 中文资源

简体中文版本

Indicatif 多阶段进度条 CLI

用 indicatif 渲染多阶段 CLI 进度:两条进度条并列展示、suspend 式 println 让日志与进度共存、短任务退化为 spinner。

阅读英文原文

示例说明

终端进度渲染的难点是进度条与普通日志抢同一块屏幕。MultiProgress 管理多条进度条的布局,println 方法在打印日志行时暂停进度重绘——输出交错不再撕裂。不同阶段选不同形态:长任务用带 ETA 的进度条,毫秒级收尾用 spinner,避免为微小操作渲染完整条带。

技术要点

  • MultiProgress 统一布局多条进度条
  • println 打日志时暂停进度重绘
  • 短任务退化 spinner 减少噪音

代码解读

两条进度条分别走四百毫秒与三百毫秒的模拟阶段,样式模板定制字符集与配色;inc 逐项推进,finish_with_message 收尾并定格消息。checksum 的 wrapping_add 展示阶段产出。最后的 clear 清理终端残留,脚本输出干净结束。

使用建议

进度刷新频率默认限速已合理无需自调。非 TTY 环境自动降级为静默。长时间任务配 suspend 写日志比清屏打印更稳妥。

代码示例

use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
// progress logging: println pauses bar redraw to keep output clean
use std::time::Duration;

fn make_style(template: &str) -> ProgressStyle {
    ProgressStyle::with_template(template)
        .unwrap()
        .progress_chars("=>-")
}

async fn process_stage(
    bar: &ProgressBar,
    total: u64,
    item_ms: u64,
) -> u64 {
    let mut checksum = 0u64;
    for i in 0..total {
        tokio::time::sleep(Duration::from_millis(item_ms)).await;
        checksum = checksum.wrapping_add(i * 31);
        bar.inc(1);
    }
    checksum
}

#[tokio::main]
async fn main() {
    let multi = MultiProgress::new();

    let download = multi.add(ProgressBar::new(40));
    download.set_style(make_style(
        "[{bar:40.cyan/blue}] {pos}/{len} files ({eta})",
    ));

    let verify = multi.add(ProgressBar::new(20));
    verify.set_style(make_style(
        "[{bar:40.green/white}] {pos}/{len} chunks {msg}",
    ));

    // 进度条与日志共存:suspend 暂停渲染打印普通输出
    multi.println("starting pipeline").unwrap();

    let sum1 = process_stage(&download, 40, 10).await;
    download.finish_with_message("download complete");

    verify.set_message("verifying");
    let sum2 = process_stage(&verify, 20, 15).await;

    multi.println(format!("checksums: {sum1} / {sum2}")).unwrap();

    // 短任务用 spinner:避免为毫秒级操作渲染完整进度条
    let spinner = multi.add(ProgressBar::new_spinner());
    spinner.set_style(make_style("{spinner:.magenta} {msg}"));
    spinner.set_message("finalizing");
    tokio::time::sleep(Duration::from_millis(300)).await;
    spinner.finish_with_message("done");

    multi.clear().unwrap();
}