pub mod group;
pub use group::{Group, Task};
use std::fmt::Display;
use std::future::Future;
use std::io::{IsTerminal, Write};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use crossterm::{cursor, QueueableCommand};
use futures_lite::stream::Pending;
use futures_lite::{stream, Stream};
use crate::bar::Bar;
use crate::layout::{Layout, RenderContext};
use crate::spinner::Ticks;
use crate::term::{clear_line, CursorGuard};
use crate::Theme;
pub struct ProgressBuilder<'a, F, M, P> {
inner: F,
bar: Bar<'a>,
bar_width: usize,
ticks: Ticks<'a>,
messages: M,
progress: P,
spinner_char: Option<char>,
label: Option<String>,
current_progress: f64,
with_elapsed_time: bool,
start: Option<Instant>,
dirty: bool,
render_buf: String,
layout: Layout,
guard: CursorGuard,
}
impl<'a, F, M, P> ProgressBuilder<'a, F, M, P> {
pub fn with_label(mut self, label: impl Display) -> Self {
self.label = Some(label.to_string());
self.dirty = true;
self
}
pub fn with_messages<S>(self, messages: S) -> ProgressBuilder<'a, F, S, P>
where
S: Stream + Unpin,
S::Item: Display,
{
ProgressBuilder {
inner: self.inner,
bar: self.bar,
bar_width: self.bar_width,
ticks: self.ticks,
messages,
progress: self.progress,
spinner_char: self.spinner_char,
label: self.label,
current_progress: self.current_progress,
with_elapsed_time: self.with_elapsed_time,
start: self.start,
dirty: self.dirty,
render_buf: self.render_buf,
layout: self.layout,
guard: self.guard,
}
}
pub fn with_elapsed_time(mut self) -> Self {
self.with_elapsed_time = true;
self.dirty = true;
self
}
pub fn with_progress<S>(self, progress: S) -> ProgressBuilder<'a, F, M, S>
where
S: Stream<Item = f64> + Unpin,
{
ProgressBuilder {
inner: self.inner,
bar: self.bar,
bar_width: self.bar_width,
ticks: self.ticks,
messages: self.messages,
progress,
spinner_char: self.spinner_char,
label: self.label,
current_progress: self.current_progress,
with_elapsed_time: self.with_elapsed_time,
start: self.start,
dirty: self.dirty,
render_buf: self.render_buf,
layout: self.layout,
guard: self.guard,
}
}
}
impl<F, M, P> Future for ProgressBuilder<'_, F, M, P>
where
F: Future + Unpin,
M: Stream + Unpin,
M::Item: Display,
P: Stream<Item = f64> + Unpin,
{
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if let Poll::Ready(spinner) = Pin::new(&mut this.ticks).poll_next(cx) {
this.spinner_char = spinner;
this.dirty = true;
}
while let Poll::Ready(Some(msg)) = Pin::new(&mut this.messages).poll_next(cx) {
this.label = Some(msg.to_string());
this.dirty = true;
}
while let Poll::Ready(Some(f)) = Pin::new(&mut this.progress).poll_next(cx) {
this.current_progress = f.clamp(0.0, 1.0);
this.dirty = true;
}
let item = Pin::new(&mut this.inner).poll(cx);
if this.guard.is_tty {
match item {
Poll::Pending if this.dirty => {
this.dirty = false;
let elapsed = if this.with_elapsed_time {
this.start.get_or_insert_with(Instant::now).elapsed()
} else {
Duration::ZERO
};
let ctx = RenderContext {
spinner: this.spinner_char,
elapsed,
show_elapsed: this.with_elapsed_time,
bar: &this.bar,
bar_width: this.bar_width,
progress: Some(this.current_progress),
label: None,
message: this.label.as_deref(),
spinner_style: owo_colors::Style::new(),
annotation_style: owo_colors::Style::new(),
};
this.render_buf.clear();
this.layout.render(&ctx, &mut this.render_buf);
let mut stdout = std::io::stdout().lock();
let _ = clear_line(&mut stdout);
let _ = stdout.queue(cursor::Hide);
let _ = stdout.write_all(this.render_buf.as_bytes());
let _ = stdout.flush();
}
Poll::Ready(_) => {
let mut stdout = std::io::stdout().lock();
let _ = clear_line(&mut stdout);
let _ = stdout.queue(cursor::Show);
let _ = stdout.flush();
}
_ => {}
}
}
item
}
}
pub trait FutureExt: Future {
fn progress<'a>(
self,
theme: impl Into<Theme<'a>>,
) -> ProgressBuilder<'a, Self, Pending<&'static str>, Pending<f64>>
where
Self: Sized,
{
let theme = theme.into();
let bar_width = theme.effective_bar_width();
ProgressBuilder {
inner: self,
bar: theme.bar,
bar_width,
ticks: theme.spinner.ticks(),
messages: stream::pending(),
progress: stream::pending(),
spinner_char: None,
label: None,
current_progress: 0.0,
with_elapsed_time: false,
start: None,
dirty: true,
render_buf: String::new(),
layout: theme.layout,
guard: CursorGuard {
is_tty: std::io::stdout().is_terminal(),
},
}
}
fn with_label<'a>(self, label: impl Into<String>) -> Task<'a, Self>
where
Self: Sized,
{
Task::from(self).with_label(label)
}
fn with_messages<'a, S, D>(self, messages: S) -> Task<'a, Self>
where
Self: Sized,
S: Stream<Item = D> + Unpin + 'a,
D: Display + 'a,
{
Task::from(self).with_messages(messages)
}
fn with_progress<'a, S>(self, progress: S) -> Task<'a, Self>
where
Self: Sized,
S: Stream<Item = f64> + Unpin + 'a,
{
Task::from(self).with_progress(progress)
}
}
impl<F> FutureExt for F where F: Future {}