1use std::io;
2use std::ops::{Add, AddAssign, Sub};
3use std::slice::SliceIndex;
4use std::sync::{Arc, RwLock, RwLockWriteGuard};
5use std::thread::panicking;
6use std::time::Duration;
7#[cfg(not(target_arch = "wasm32"))]
8use std::time::Instant;
9
10use console::Term;
11#[cfg(target_arch = "wasm32")]
12use instant::Instant;
13
14use crate::multi::{MultiProgressAlignment, MultiState};
15use crate::TermLike;
16
17#[derive(Debug)]
25pub struct ProgressDrawTarget {
26 kind: TargetKind,
27}
28
29impl ProgressDrawTarget {
30 pub fn stdout() -> Self {
34 Self::term(Term::buffered_stdout(), 20)
35 }
36
37 pub fn stderr() -> Self {
42 Self::term(Term::buffered_stderr(), 20)
43 }
44
45 pub fn stdout_with_hz(refresh_rate: u8) -> Self {
49 Self::term(Term::buffered_stdout(), refresh_rate)
50 }
51
52 pub fn stderr_with_hz(refresh_rate: u8) -> Self {
56 Self::term(Term::buffered_stderr(), refresh_rate)
57 }
58
59 pub(crate) fn new_remote(state: Arc<RwLock<MultiState>>, idx: usize) -> Self {
60 Self {
61 kind: TargetKind::Multi { state, idx },
62 }
63 }
64
65 pub fn term(term: Term, refresh_rate: u8) -> Self {
74 Self {
75 kind: TargetKind::Term {
76 term,
77 last_line_count: VisualLines::default(),
78 rate_limiter: RateLimiter::new(refresh_rate),
79 draw_state: DrawState::default(),
80 },
81 }
82 }
83
84 pub fn term_like(term_like: Box<dyn TermLike>) -> Self {
86 Self {
87 kind: TargetKind::TermLike {
88 inner: term_like,
89 last_line_count: VisualLines::default(),
90 rate_limiter: None,
91 draw_state: DrawState::default(),
92 },
93 }
94 }
95
96 pub fn term_like_with_hz(term_like: Box<dyn TermLike>, refresh_rate: u8) -> Self {
99 Self {
100 kind: TargetKind::TermLike {
101 inner: term_like,
102 last_line_count: VisualLines::default(),
103 rate_limiter: Option::from(RateLimiter::new(refresh_rate)),
104 draw_state: DrawState::default(),
105 },
106 }
107 }
108
109 pub fn hidden() -> Self {
113 Self {
114 kind: TargetKind::Hidden,
115 }
116 }
117
118 pub fn is_hidden(&self) -> bool {
123 match self.kind {
124 TargetKind::Hidden => true,
125 TargetKind::Term { ref term, .. } => !term.is_term(),
126 TargetKind::Multi { ref state, .. } => state.read().unwrap().is_hidden(),
127 _ => false,
128 }
129 }
130
131 pub(crate) fn width(&self) -> Option<u16> {
133 match self.kind {
134 TargetKind::Term { ref term, .. } => Some(term.size().1),
135 TargetKind::Multi { ref state, .. } => state.read().unwrap().width(),
136 TargetKind::TermLike { ref inner, .. } => Some(inner.width()),
137 TargetKind::Hidden => None,
138 }
139 }
140
141 pub(crate) fn mark_zombie(&self) {
144 if let TargetKind::Multi { idx, state } = &self.kind {
145 state.write().unwrap().mark_zombie(*idx);
146 }
147 }
148
149 pub(crate) fn drawable(&mut self, force_draw: bool, now: Instant) -> Option<Drawable<'_>> {
151 match &mut self.kind {
152 TargetKind::Term {
153 term,
154 last_line_count,
155 rate_limiter,
156 draw_state,
157 } => {
158 if !term.is_term() {
159 return None;
160 }
161
162 match force_draw || rate_limiter.allow(now) {
163 true => Some(Drawable::Term {
164 term,
165 last_line_count,
166 draw_state,
167 }),
168 false => None, }
170 }
171 TargetKind::Multi { idx, state, .. } => {
172 let state = state.write().unwrap();
173 Some(Drawable::Multi {
174 idx: *idx,
175 state,
176 force_draw,
177 now,
178 })
179 }
180 TargetKind::TermLike {
181 inner,
182 last_line_count,
183 rate_limiter,
184 draw_state,
185 } => match force_draw || rate_limiter.as_mut().map_or(true, |r| r.allow(now)) {
186 true => Some(Drawable::TermLike {
187 term_like: &**inner,
188 last_line_count,
189 draw_state,
190 }),
191 false => None, },
193 _ => None,
195 }
196 }
197
198 pub(crate) fn disconnect(&self, now: Instant) {
200 match self.kind {
201 TargetKind::Term { .. } => {}
202 TargetKind::Multi { idx, ref state, .. } => {
203 let state = state.write().unwrap();
204 let _ = Drawable::Multi {
205 state,
206 idx,
207 force_draw: true,
208 now,
209 }
210 .clear();
211 }
212 TargetKind::Hidden => {}
213 TargetKind::TermLike { .. } => {}
214 };
215 }
216
217 pub(crate) fn remote(&self) -> Option<(&Arc<RwLock<MultiState>>, usize)> {
218 match &self.kind {
219 TargetKind::Multi { state, idx } => Some((state, *idx)),
220 _ => None,
221 }
222 }
223
224 pub(crate) fn adjust_last_line_count(&mut self, adjust: LineAdjust) {
225 self.kind.adjust_last_line_count(adjust);
226 }
227}
228
229#[derive(Debug)]
230enum TargetKind {
231 Term {
232 term: Term,
233 last_line_count: VisualLines,
234 rate_limiter: RateLimiter,
235 draw_state: DrawState,
236 },
237 Multi {
238 state: Arc<RwLock<MultiState>>,
239 idx: usize,
240 },
241 Hidden,
242 TermLike {
243 inner: Box<dyn TermLike>,
244 last_line_count: VisualLines,
245 rate_limiter: Option<RateLimiter>,
246 draw_state: DrawState,
247 },
248}
249
250impl TargetKind {
251 fn adjust_last_line_count(&mut self, adjust: LineAdjust) {
253 let last_line_count = match self {
254 Self::Term {
255 last_line_count, ..
256 } => last_line_count,
257 Self::TermLike {
258 last_line_count, ..
259 } => last_line_count,
260 _ => return,
261 };
262
263 match adjust {
264 LineAdjust::Clear(count) => *last_line_count = last_line_count.saturating_add(count),
265 LineAdjust::Keep(count) => *last_line_count = last_line_count.saturating_sub(count),
266 }
267 }
268}
269
270pub(crate) enum Drawable<'a> {
271 Term {
272 term: &'a Term,
273 last_line_count: &'a mut VisualLines,
274 draw_state: &'a mut DrawState,
275 },
276 Multi {
277 state: RwLockWriteGuard<'a, MultiState>,
278 idx: usize,
279 force_draw: bool,
280 now: Instant,
281 },
282 TermLike {
283 term_like: &'a dyn TermLike,
284 last_line_count: &'a mut VisualLines,
285 draw_state: &'a mut DrawState,
286 },
287}
288
289impl<'a> Drawable<'a> {
290 pub(crate) fn adjust_last_line_count(&mut self, adjust: LineAdjust) {
292 let last_line_count: &mut VisualLines = match self {
293 Drawable::Term {
294 last_line_count, ..
295 } => last_line_count,
296 Drawable::TermLike {
297 last_line_count, ..
298 } => last_line_count,
299 _ => return,
300 };
301
302 match adjust {
303 LineAdjust::Clear(count) => *last_line_count = last_line_count.saturating_add(count),
304 LineAdjust::Keep(count) => *last_line_count = last_line_count.saturating_sub(count),
305 }
306 }
307
308 pub(crate) fn state(&mut self) -> DrawStateWrapper<'_> {
309 let mut state = match self {
310 Drawable::Term { draw_state, .. } => DrawStateWrapper::for_term(draw_state),
311 Drawable::Multi { state, idx, .. } => state.draw_state(*idx),
312 Drawable::TermLike { draw_state, .. } => DrawStateWrapper::for_term(draw_state),
313 };
314
315 state.reset();
316 state
317 }
318
319 pub(crate) fn clear(mut self) -> io::Result<()> {
320 let state = self.state();
321 drop(state);
322 self.draw()
323 }
324
325 pub(crate) fn draw(self) -> io::Result<()> {
326 match self {
327 Drawable::Term {
328 term,
329 last_line_count,
330 draw_state,
331 } => draw_state.draw_to_term(term, last_line_count),
332 Drawable::Multi {
333 mut state,
334 force_draw,
335 now,
336 ..
337 } => state.draw(force_draw, None, now),
338 Drawable::TermLike {
339 term_like,
340 last_line_count,
341 draw_state,
342 } => draw_state.draw_to_term(term_like, last_line_count),
343 }
344 }
345}
346
347pub(crate) enum LineAdjust {
348 Clear(VisualLines),
350 Keep(VisualLines),
352}
353
354pub(crate) struct DrawStateWrapper<'a> {
355 state: &'a mut DrawState,
356 orphan_lines: Option<&'a mut Vec<String>>,
357}
358
359impl<'a> DrawStateWrapper<'a> {
360 pub(crate) fn for_term(state: &'a mut DrawState) -> Self {
361 Self {
362 state,
363 orphan_lines: None,
364 }
365 }
366
367 pub(crate) fn for_multi(state: &'a mut DrawState, orphan_lines: &'a mut Vec<String>) -> Self {
368 Self {
369 state,
370 orphan_lines: Some(orphan_lines),
371 }
372 }
373}
374
375impl std::ops::Deref for DrawStateWrapper<'_> {
376 type Target = DrawState;
377
378 fn deref(&self) -> &Self::Target {
379 self.state
380 }
381}
382
383impl std::ops::DerefMut for DrawStateWrapper<'_> {
384 fn deref_mut(&mut self) -> &mut Self::Target {
385 self.state
386 }
387}
388
389impl Drop for DrawStateWrapper<'_> {
390 fn drop(&mut self) {
391 if let Some(orphaned) = &mut self.orphan_lines {
392 orphaned.extend(self.state.lines.drain(..self.state.orphan_lines_count));
393 self.state.orphan_lines_count = 0;
394 }
395 }
396}
397
398#[derive(Debug)]
399struct RateLimiter {
400 interval: u16, capacity: u8,
402 prev: Instant,
403}
404
405impl RateLimiter {
407 fn new(rate: u8) -> Self {
408 Self {
409 interval: 1000 / (rate as u16), capacity: MAX_BURST,
411 prev: Instant::now(),
412 }
413 }
414
415 fn allow(&mut self, now: Instant) -> bool {
416 if now < self.prev {
417 return false;
418 }
419
420 let elapsed = now - self.prev;
421 if self.capacity == 0 && elapsed < Duration::from_millis(self.interval as u64) {
425 return false;
426 }
427
428 let (new, remainder) = (
432 elapsed.as_millis() / self.interval as u128,
433 elapsed.as_nanos() % (self.interval as u128 * 1_000_000),
434 );
435
436 self.capacity = Ord::min(MAX_BURST as u128, (self.capacity as u128) + new - 1) as u8;
439 self.prev = now
442 .checked_sub(Duration::from_nanos(remainder as u64))
443 .unwrap();
444 true
445 }
446}
447
448const MAX_BURST: u8 = 20;
449
450#[derive(Clone, Debug, Default)]
452pub(crate) struct DrawState {
453 pub(crate) lines: Vec<String>,
455 pub(crate) orphan_lines_count: usize,
459 pub(crate) move_cursor: bool,
461 pub(crate) alignment: MultiProgressAlignment,
463}
464
465impl DrawState {
466 fn draw_to_term(
467 &mut self,
468 term: &(impl TermLike + ?Sized),
469 last_line_count: &mut VisualLines,
470 ) -> io::Result<()> {
471 if panicking() {
472 return Ok(());
473 }
474
475 if !self.lines.is_empty() && self.move_cursor {
476 term.move_cursor_up(last_line_count.as_usize())?;
477 } else {
478 let n = last_line_count.as_usize();
480 term.move_cursor_up(n.saturating_sub(1))?;
481 for i in 0..n {
482 term.clear_line()?;
483 if i + 1 != n {
484 term.move_cursor_down(1)?;
485 }
486 }
487 term.move_cursor_up(n.saturating_sub(1))?;
488 }
489
490 let width = term.width() as usize;
491 let visual_lines = self.visual_line_count(.., width);
492 let shift = match self.alignment {
493 MultiProgressAlignment::Bottom if visual_lines < *last_line_count => {
494 let shift = *last_line_count - visual_lines;
495 for _ in 0..shift.as_usize() {
496 term.write_line("")?;
497 }
498 shift
499 }
500 _ => VisualLines::default(),
501 };
502
503 let term_height = term.height() as usize;
504 let term_width = term.width() as usize;
505 let len = self.lines.len();
506 debug_assert!(self.orphan_lines_count <= self.lines.len());
507 let orphan_visual_line_count =
508 self.visual_line_count(..self.orphan_lines_count, term_width);
509 let mut real_len = VisualLines::default();
510 let mut last_line_filler = 0;
511 for (idx, line) in self.lines.iter().enumerate() {
512 let line_width = console::measure_text_width(line);
513 let diff = if line.is_empty() {
514 1
516 } else {
517 let terminal_len = (line_width as f64 / term_width as f64).ceil() as usize;
520
521 usize::max(terminal_len, 1)
526 }
527 .into();
528 if self.orphan_lines_count <= idx {
530 debug_assert!(orphan_visual_line_count <= real_len);
532 if real_len - orphan_visual_line_count + diff > term_height.into() {
534 break;
535 }
536 }
537 real_len += diff;
538 if idx != 0 {
539 term.write_line("")?;
540 }
541 term.write_str(line)?;
542 if idx + 1 == len {
543 last_line_filler = term_width.saturating_sub(line_width);
546 }
547 }
548 term.write_str(&" ".repeat(last_line_filler))?;
549
550 term.flush()?;
551 *last_line_count = real_len - orphan_visual_line_count + shift;
552 Ok(())
553 }
554
555 fn reset(&mut self) {
556 self.lines.clear();
557 self.orphan_lines_count = 0;
558 }
559
560 pub(crate) fn visual_line_count(
561 &self,
562 range: impl SliceIndex<[String], Output = [String]>,
563 width: usize,
564 ) -> VisualLines {
565 visual_line_count(&self.lines[range], width)
566 }
567}
568
569#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
570pub(crate) struct VisualLines(usize);
571
572impl VisualLines {
573 pub(crate) fn saturating_add(&self, other: Self) -> Self {
574 Self(self.0.saturating_add(other.0))
575 }
576
577 pub(crate) fn saturating_sub(&self, other: Self) -> Self {
578 Self(self.0.saturating_sub(other.0))
579 }
580
581 pub(crate) fn as_usize(&self) -> usize {
582 self.0
583 }
584}
585
586impl Add for VisualLines {
587 type Output = Self;
588
589 fn add(self, rhs: Self) -> Self::Output {
590 Self(self.0 + rhs.0)
591 }
592}
593
594impl AddAssign for VisualLines {
595 fn add_assign(&mut self, rhs: Self) {
596 self.0 += rhs.0;
597 }
598}
599
600impl<T: Into<usize>> From<T> for VisualLines {
601 fn from(value: T) -> Self {
602 Self(value.into())
603 }
604}
605
606impl Sub for VisualLines {
607 type Output = Self;
608
609 fn sub(self, rhs: Self) -> Self::Output {
610 Self(self.0 - rhs.0)
611 }
612}
613
614pub(crate) fn visual_line_count(lines: &[impl AsRef<str>], width: usize) -> VisualLines {
617 let mut real_lines = 0;
618 for line in lines {
619 let effective_line_length = console::measure_text_width(line.as_ref());
620 real_lines += usize::max(
621 (effective_line_length as f64 / width as f64).ceil() as usize,
622 1,
623 );
624 }
625
626 real_lines.into()
627}
628
629#[cfg(test)]
630mod tests {
631 use crate::{MultiProgress, ProgressBar, ProgressDrawTarget};
632
633 #[test]
634 fn multi_is_hidden() {
635 let mp = MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
636
637 let pb = mp.add(ProgressBar::new(100));
638 assert!(mp.is_hidden());
639 assert!(pb.is_hidden());
640 }
641
642 #[test]
643 fn real_line_count_test() {
644 #[derive(Debug)]
645 struct Case {
646 lines: &'static [&'static str],
647 expectation: usize,
648 width: usize,
649 }
650
651 let lines_and_expectations = [
652 Case {
653 lines: &["1234567890"],
654 expectation: 1,
655 width: 10,
656 },
657 Case {
658 lines: &["1234567890"],
659 expectation: 2,
660 width: 5,
661 },
662 Case {
663 lines: &["1234567890"],
664 expectation: 3,
665 width: 4,
666 },
667 Case {
668 lines: &["1234567890"],
669 expectation: 4,
670 width: 3,
671 },
672 Case {
673 lines: &["1234567890", "", "1234567890"],
674 expectation: 3,
675 width: 10,
676 },
677 Case {
678 lines: &["1234567890", "", "1234567890"],
679 expectation: 5,
680 width: 5,
681 },
682 Case {
683 lines: &["1234567890", "", "1234567890"],
684 expectation: 7,
685 width: 4,
686 },
687 Case {
688 lines: &["aaaaaaaaaaaaa", "", "bbbbbbbbbbbbbbbbb", "", "ccccccc"],
689 expectation: 8,
690 width: 7,
691 },
692 Case {
693 lines: &["", "", "", "", ""],
694 expectation: 5,
695 width: 6,
696 },
697 Case {
698 lines: &["\u{1b}[1m\u{1b}[1m\u{1b}[1m", "\u{1b}[1m\u{1b}[1m\u{1b}[1m"],
700 expectation: 2,
701 width: 5,
702 },
703 Case {
704 lines: &[
706 "a\u{1b}[1m\u{1b}[1m\u{1b}[1ma",
707 "a\u{1b}[1m\u{1b}[1m\u{1b}[1ma",
708 ],
709 expectation: 2,
710 width: 5,
711 },
712 Case {
713 lines: &[
715 "aa\u{1b}[1m\u{1b}[1m\u{1b}[1mabcd",
716 "aa\u{1b}[1m\u{1b}[1m\u{1b}[1mabcd",
717 ],
718 expectation: 4,
719 width: 5,
720 },
721 ];
722
723 for case in lines_and_expectations.iter() {
724 let result = super::visual_line_count(case.lines, case.width);
725 assert_eq!(result, case.expectation.into(), "case: {:?}", case);
726 }
727 }
728}