pub mod group;
pub mod join;
pub use group::Group;
pub use join::{join, Join};
use std::fmt::Display;
use std::future::Future;
use std::io::IsTerminal;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use futures_lite::stream::Pending;
use futures_lite::{stream, Stream};
use owo_colors::Style;
use crate::line::{FrameContext, Line};
use crate::progressive::Progressive;
use crate::spinner::Ticks;
use crate::state::State;
use crate::term::CursorGuard;
use crate::Theme;
pub(super) struct Rendering<'a> {
pub(super) line: Line<'a>,
pub(super) ticks: Ticks<'a>,
pub(super) spinner_char: Option<char>,
pub(super) spinner_style: Style,
pub(super) annotation_style: Style,
pub(super) is_tty: bool,
pub(super) _guard: CursorGuard,
}
pub(super) enum RenderingState<'a> {
Pending,
Active(Rendering<'a>),
Detached,
}
pub struct ProgressFuture<'a, F, M = Pending<&'static str>, P = Pending<f64>> {
inner: F,
messages: M,
progress: P,
state: State,
theme_override: Option<Theme<'a>>,
spinner_style_override: Option<Style>,
annotation_style_override: Option<Style>,
rendering: RenderingState<'a>,
}
impl<F> ProgressFuture<'_, F> {
pub fn new(inner: F) -> Self {
Self {
inner,
messages: stream::pending(),
progress: stream::pending(),
state: State::new(),
theme_override: None,
spinner_style_override: None,
annotation_style_override: None,
rendering: RenderingState::Pending,
}
}
}
impl<'a, F, M, P> ProgressFuture<'a, F, M, P> {
pub fn with_label(mut self, label: impl Display) -> Self {
self.state.set_label(label.to_string());
self
}
pub fn with_messages<S>(self, messages: S) -> ProgressFuture<'a, F, S, P>
where
S: Stream + Unpin,
S::Item: Display,
{
ProgressFuture {
inner: self.inner,
messages,
progress: self.progress,
state: self.state,
theme_override: self.theme_override,
spinner_style_override: self.spinner_style_override,
annotation_style_override: self.annotation_style_override,
rendering: self.rendering,
}
}
pub fn with_elapsed_time(mut self) -> Self {
self.state.enable_elapsed_time();
self
}
pub fn with_progress<S>(self, progress: S) -> ProgressFuture<'a, F, M, S>
where
S: Stream<Item = f64> + Unpin,
{
ProgressFuture {
inner: self.inner,
messages: self.messages,
progress,
state: self.state,
theme_override: self.theme_override,
spinner_style_override: self.spinner_style_override,
annotation_style_override: self.annotation_style_override,
rendering: self.rendering,
}
}
pub fn with_theme(mut self, theme: impl Into<Theme<'a>>) -> Self {
self.theme_override = Some(theme.into());
self
}
pub fn with_spinner_style(mut self, style: Style) -> Self {
self.spinner_style_override = Some(style);
self
}
pub fn with_annotation_style(mut self, style: Style) -> Self {
self.annotation_style_override = Some(style);
self
}
}
impl<'a, F, M, P> Progressive<'a> for ProgressFuture<'a, F, M, P> {
fn label(&self) -> Option<&str> {
self.state.label()
}
fn message(&self) -> Option<&str> {
self.state.message()
}
fn progress(&self) -> Option<f64> {
self.state.progress()
}
fn bytes_done(&self) -> u64 {
self.state.bytes_done()
}
fn bytes_total(&self) -> Option<u64> {
self.state.bytes_total()
}
fn rate(&self) -> Option<f64> {
self.state.rate()
}
fn detach_rendering(&mut self) {
self.rendering = RenderingState::Detached;
}
fn theme(&self) -> Option<&Theme<'a>> {
self.theme_override.as_ref()
}
fn spinner_style(&self) -> Option<Style> {
self.spinner_style_override
}
fn annotation_style(&self) -> Option<Style> {
self.annotation_style_override
}
fn show_elapsed_time(&self) -> Option<bool> {
if self.state.with_elapsed_time {
Some(true)
} else {
None
}
}
}
impl<F, M, P> Future for ProgressFuture<'_, 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 matches!(this.rendering, RenderingState::Pending) {
let theme = this.theme_override.clone().unwrap_or_default();
let is_tty = std::io::stdout().is_terminal();
let ticks = theme.spinner.ticks();
let line = Line::new(&theme);
this.state.set_progress(0.0);
this.rendering = RenderingState::Active(Rendering {
line,
ticks,
spinner_char: None,
spinner_style: this.spinner_style_override.unwrap_or_default(),
annotation_style: this.annotation_style_override.unwrap_or_default(),
is_tty,
_guard: CursorGuard { is_tty },
});
}
let mut dirty = false;
if let RenderingState::Active(r) = &mut this.rendering {
if let Poll::Ready(ch) = Pin::new(&mut r.ticks).poll_next(cx) {
r.spinner_char = ch;
dirty = true;
}
}
while let Poll::Ready(Some(msg)) = Pin::new(&mut this.messages).poll_next(cx) {
this.state.set_message(msg.to_string());
dirty = true;
}
while let Poll::Ready(Some(p)) = Pin::new(&mut this.progress).poll_next(cx) {
this.state.set_progress(p.clamp(0.0, 1.0));
dirty = true;
}
let item = Pin::new(&mut this.inner).poll(cx);
if let RenderingState::Active(r) = &mut this.rendering {
match item {
Poll::Pending if dirty => {
let elapsed = if this.state.with_elapsed_time {
this.state.elapsed()
} else {
Duration::ZERO
};
let frame = FrameContext {
spinner_char: r.spinner_char,
elapsed,
show_elapsed: this.state.with_elapsed_time,
spinner_style: r.spinner_style,
annotation_style: r.annotation_style,
};
r.line.standalone_render(&this.state, &frame, r.is_tty);
}
Poll::Ready(_) => Line::standalone_clear(r.is_tty),
_ => {}
}
}
item
}
}
pub trait FutureExt: Future {
fn progress<'a>(self, theme: impl Into<Theme<'a>>) -> ProgressFuture<'a, Self>
where
Self: Sized,
{
self.progressive().with_theme(theme)
}
fn progressive<'a>(self) -> ProgressFuture<'a, Self>
where
Self: Sized,
{
ProgressFuture::new(self)
}
fn with_label<'a>(self, label: impl Display) -> ProgressFuture<'a, Self>
where
Self: Sized,
{
self.progressive().with_label(label)
}
fn with_elapsed_time<'a>(self) -> ProgressFuture<'a, Self>
where
Self: Sized,
{
self.progressive().with_elapsed_time()
}
fn with_messages<'a, S>(self, messages: S) -> ProgressFuture<'a, Self, S>
where
Self: Sized,
S: Stream + Unpin,
S::Item: Display,
{
self.progressive().with_messages(messages)
}
fn with_progress<'a, S>(self, progress: S) -> ProgressFuture<'a, Self, Pending<&'static str>, S>
where
Self: Sized,
S: Stream<Item = f64> + Unpin,
{
self.progressive().with_progress(progress)
}
}
impl<F> FutureExt for F where F: Future {}