Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/progress/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ impl ProgressJobBuilder {
operations_total: Mutex::new(None),
operation_index: Mutex::new(0),
operation_start: Mutex::new(Instant::now()),
last_text_output: Mutex::new(None),
}
}

Expand Down Expand Up @@ -205,6 +206,9 @@ pub struct ProgressJob {
pub(crate) operation_index: Mutex<usize>,
/// Start time of the current operation (for ETA calculation after next_operation)
pub(crate) operation_start: Mutex<Instant>,
/// Last rendered text-mode line. Used to suppress consecutive identical
/// emissions when multiple props are updated in quick succession.
pub(crate) last_text_output: Mutex<Option<String>>,
}

impl ProgressJob {
Expand Down
17 changes: 16 additions & 1 deletion src/progress/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,13 @@ pub fn refresh() -> Result<bool> {
}

/// Performs one refresh cycle without loop control.
///
/// In `ProgressOutput::Text` mode this is a no-op: text mode emits a fresh
/// line for each job update, so a full-frame redraw would only repeat content
/// already on the wire (and emit cursor-movement escape codes that look like
/// garbage in non-TTY logs such as CI).
pub fn refresh_once() -> Result<()> {
if is_disabled() || output() == ProgressOutput::Quiet {
if is_disabled() || matches!(output(), ProgressOutput::Quiet | ProgressOutput::Text) {
return Ok(());
}
let _refresh_guard = REFRESH_LOCK.lock().unwrap();
Expand Down Expand Up @@ -300,6 +305,16 @@ pub fn render_text_mode(job: &ProgressJob) -> Result<()> {
} else {
output
};
// Skip writing if this job's last text-mode line was identical. Callers
// often update several props in a row (e.g. `message` then `cur`); each
// call hits this path, but if the rendered line is unchanged there's no
// information to add — emitting it again just makes CI logs noisier.
let mut last = job.last_text_output.lock().unwrap();
if last.as_deref() == Some(final_output.as_str()) {
return Ok(());
}
*last = Some(final_output.clone());
drop(last);
let _guard = TERM_LOCK.lock().unwrap();
term().write_line(&final_output)?;
drop(_guard);
Expand Down