strides/
future.rs

1//! Spinner integration for futures.
2
3pub mod monitored;
4
5pub use monitored::Monitored;
6
7use std::future::Future;
8use std::io::Write;
9use std::pin::Pin;
10use std::task::{Context, Poll};
11
12use futures_lite::{FutureExt as _, Stream};
13
14use crate::spinner::Spinner;
15use crate::term::clear_line;
16
17/// Future for the [`progress`](FutureExt::progress) method.
18pub struct Progress<F, T> {
19    /// Wrapped future.
20    inner: F,
21    /// Spinner tick stream.
22    ticks: T,
23    /// Annotation for the future.
24    message: String,
25    /// Current spinner character.
26    spinner: Option<char>,
27}
28
29impl<F, T> Future for Progress<F, T>
30where
31    F: Future + Unpin,
32    T: Stream<Item = char> + Unpin,
33{
34    type Output = F::Output;
35
36    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
37        let this = self.get_mut();
38        let ticks = Pin::new(&mut this.ticks);
39
40        if let Poll::Ready(spinner) = ticks.poll_next(cx) {
41            this.spinner = spinner;
42        }
43
44        let _ = clear_line(&mut std::io::stdout());
45
46        let item = this.inner.poll(cx);
47
48        if matches!(item, Poll::Pending) {
49            if let Some(spinner) = &this.spinner {
50                print!("{spinner}");
51            }
52
53            print!(" {}", this.message);
54        }
55
56        std::io::stdout().flush().expect("flushing");
57        item
58    }
59}
60
61pub trait FutureExt: Future {
62    fn progress(
63        self,
64        spinner: Spinner,
65        message: impl std::fmt::Display,
66    ) -> Progress<Self, impl Stream<Item = char>>
67    where
68        Self: Sized,
69    {
70        Progress {
71            inner: self,
72            ticks: spinner.ticks(),
73            message: message.to_string(),
74            spinner: None,
75        }
76    }
77}
78
79impl<F> FutureExt for F where F: Future {}