1pub 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
17pub struct Progress<F, T> {
19 inner: F,
21 ticks: T,
23 message: String,
25 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 {}