indicatif/multi.rs
1use std::fmt::{Debug, Formatter};
2use std::io;
3use std::sync::{Arc, RwLock};
4use std::thread::panicking;
5#[cfg(not(target_arch = "wasm32"))]
6use std::time::Instant;
7
8use crate::draw_target::{
9 visual_line_count, DrawState, DrawStateWrapper, LineAdjust, LineType, ProgressDrawTarget,
10 VisualLines,
11};
12use crate::progress_bar::ProgressBar;
13#[cfg(all(target_arch = "wasm32", feature = "wasmbind"))]
14use web_time::Instant;
15
16/// Manages multiple progress bars, potentially from different threads.
17///
18/// # `ProgressBar` lifecycle in a `MultiProgress`
19/// This section was written to help you avoid unexpected behavior when using `MultiProgress`. The two most common
20/// issues that users face are:
21///
22/// 1. Inadvertent draws prior to adding to the `MultiProgress`
23/// 2. `ProgressBar`s getting dropped too soon
24///
25/// ## Inadvertent draws
26/// `MultiProgress` can only coordinate drawing progress bars on the screen if it is aware of them. A common bug is to
27/// create a `ProgressBar`, accidentally cause it to draw (or tick), and then later add it to the `MultiProgress`. This
28/// can lead to screen corruption since `MultiProgress` has no way to "undo" whatever the `ProgressBar` did before
29/// the bar came under its purview.
30///
31/// Here's an example of potentially problematic code. The bar is created at (1) but added to the `MultiProgress` at
32/// (2).
33///
34/// ```rust,ignore
35/// // Bad code, do not use!
36/// let m = MultiProgress::new();
37/// let pb = ProgressBar::new(100); // (1)
38/// // It's awfully tempting to touch
39/// // `pb`, before it's added to `m`...
40/// m.add(pb); // (2)
41/// ```
42///
43/// Instead, create the `ProgressBar` and add it to the `MultiProgress` as a single call:
44///
45/// ```rust,ignore
46/// // Better code
47/// let m = MultiProgress::new();
48/// let pb = m.add(ProgressBar::new(100));
49/// // Then style/exercise it as you please:
50/// // e.g. pb.set_style()
51/// ```
52///
53/// Future work may deprecate the "bad" API and steer users toward the "good" model. See <https://github.com/console-rs/indicatif/issues/677> for example.
54///
55/// ## Premature drops
56/// Consider this code, with an overall "total" `ProgressBar` and an individual `ProgressBar` for each of 5 jobs. The
57/// intention is that when each job bar finishes, it stays on the screen with the "DONE!" message. Also, during job
58/// processing, we call [`MultiProgress::suspend`] to temporarily clear the terminal and manually print some extra
59/// messages.
60///
61/// ```rust
62/// use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
63/// use std::borrow::Cow;
64///
65/// fn main() {
66/// let m = MultiProgress::new();
67/// let sty = ProgressStyle::with_template(
68/// "{prefix:<10} [{elapsed}] {bar:20.red/blue} {pos:>7}/{len:7} {msg}",
69/// )
70/// .unwrap()
71/// .progress_chars("##-");
72///
73/// let total = m.add(ProgressBar::new(10));
74/// total.set_style(sty.clone());
75/// total.set_prefix("total");
76/// for i in 0..5 {
77/// let name = format!("Job #{i}");
78/// let pb = m.insert_before(
79/// &total,
80/// ProgressBar::new(3).with_finish(ProgressFinish::WithMessage(Cow::Borrowed("DONE!"))),
81/// );
82///
83/// pb.set_style(sty.clone());
84/// pb.set_prefix(name);
85/// for _ in 0..3 {
86/// // Temporarily clear the screen so we can print a message to the terminal
87/// m.suspend(|| {
88/// eprintln!("from job #{i}...");
89/// });
90///
91/// pb.inc(1);
92/// }
93/// pb.finish_using_style();
94/// total.inc(1);
95/// }
96///
97/// total.finish();
98/// }
99/// ```
100///
101///
102/// The issue is that at the end of each loop iteration, `pb` is dropped. Conceptually `MultiProgress` only maintains
103/// weak references to `ProgressBar`s. At the next loop iteration, `suspend` causes `MultiProgress` to clear the screen.
104/// `MultiProgress`' "zombie" algorithm ensures the dropped (zombie) bar is not left behind on the screen. But
105/// `MultiProgress` can't reconstitute the 'finish' state (i.e. "DONE!" text), since the bar no longer exists.
106///
107/// The solution is to ensure each `ProgressBar` lives long enough:
108/// ```rust,ignore
109/// // Vec to hold handles
110/// let mut pbs = vec![];
111/// for i in 0..5 {
112/// let name = format!("Job #{i}");
113/// let pb = m.insert_before(
114/// &total,
115/// ProgressBar::new(3).with_finish(ProgressFinish::WithMessage(Cow::Borrowed("DONE!"))),
116/// );
117/// // Stash a handle to the pb to keep it alive till end of loop
118/// pbs.push(pb.clone());
119///
120/// pb.set_style(sty.clone());
121/// pb.set_prefix(name);
122/// // ... snipped ...
123/// }
124/// ```
125///
126/// ## The "zombie" algorithm
127/// The "zombie" algorithm is a compromise. If the user lets a `ProgressBar` drop, then it is taken as a strong hint that
128/// we can forget about it. But, the [`MultiProgress::println`] method advertises the ability to print a message above
129/// **all** progress bars.
130///
131/// As a compromise, `MultiProgress` will keep track of how many lines of text were last printed to the screen, even for
132/// `ProgressBar`s that have dropped. But the next time `MultiProgress` clears the screen, e.g. for a
133/// [`MultiProgress::suspend`] or [`MultiProgress::println`], any so-called "zombie lines" at the head of the list are wiped
134/// but then not re-drawn. If you really want those lines to be persisted on screen, then keep the `ProgressBar`s around
135/// longer, as described in the previous section.
136///
137#[derive(Debug, Clone)]
138pub struct MultiProgress {
139 pub(crate) state: Arc<RwLock<MultiState>>,
140}
141
142impl Default for MultiProgress {
143 fn default() -> Self {
144 Self::with_draw_target(ProgressDrawTarget::stderr())
145 }
146}
147
148impl MultiProgress {
149 /// Creates a new multi progress object.
150 ///
151 /// Progress bars added to this object by default draw directly to stderr, and refresh
152 /// a maximum of 15 times a second. To change the refresh rate [set] the [draw target] to
153 /// one with a different refresh rate.
154 ///
155 /// [set]: MultiProgress::set_draw_target
156 /// [draw target]: ProgressDrawTarget
157 pub fn new() -> Self {
158 Self::default()
159 }
160
161 /// Creates a new multi progress object with the given draw target.
162 pub fn with_draw_target(draw_target: ProgressDrawTarget) -> Self {
163 Self {
164 state: Arc::new(RwLock::new(MultiState::new(draw_target))),
165 }
166 }
167
168 /// Sets a different draw target for the multiprogress bar.
169 ///
170 /// Use [`MultiProgress::with_draw_target`] to set the draw target during creation.
171 pub fn set_draw_target(&self, target: ProgressDrawTarget) {
172 let mut state = self.state.write().unwrap();
173 state.draw_target.disconnect(Instant::now());
174 state.draw_target = target;
175 }
176
177 /// Set whether we should try to move the cursor when possible instead of clearing lines.
178 ///
179 /// This can reduce flickering, but do not enable it if you intend to change the number of
180 /// progress bars.
181 pub fn set_move_cursor(&self, move_cursor: bool) {
182 self.state
183 .write()
184 .unwrap()
185 .draw_target
186 .set_move_cursor(move_cursor);
187 }
188
189 /// Set alignment flag
190 pub fn set_alignment(&self, alignment: MultiProgressAlignment) {
191 self.state.write().unwrap().alignment = alignment;
192 }
193
194 /// Adds a progress bar.
195 ///
196 /// The progress bar added will have the draw target changed to a
197 /// remote draw target that is intercepted by the multi progress
198 /// object overriding custom [`ProgressDrawTarget`] settings.
199 ///
200 /// The progress bar will be positioned below all other bars currently
201 /// in the [`MultiProgress`].
202 ///
203 /// Adding a progress bar that is already a member of the [`MultiProgress`]
204 /// will have no effect.
205 pub fn add(&self, pb: ProgressBar) -> ProgressBar {
206 self.internalize(InsertLocation::End, pb)
207 }
208
209 /// Inserts a progress bar.
210 ///
211 /// The progress bar inserted at position `index` will have the draw
212 /// target changed to a remote draw target that is intercepted by the
213 /// multi progress object overriding custom [`ProgressDrawTarget`] settings.
214 ///
215 /// If `index >= MultiProgressState::objects.len()`, the progress bar
216 /// is added to the end of the list.
217 ///
218 /// Inserting a progress bar that is already a member of the [`MultiProgress`]
219 /// will have no effect.
220 pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar {
221 self.internalize(InsertLocation::Index(index), pb)
222 }
223
224 /// Inserts a progress bar from the back.
225 ///
226 /// The progress bar inserted at position `MultiProgressState::objects.len() - index`
227 /// will have the draw target changed to a remote draw target that is
228 /// intercepted by the multi progress object overriding custom
229 /// [`ProgressDrawTarget`] settings.
230 ///
231 /// If `index >= MultiProgressState::objects.len()`, the progress bar
232 /// is added to the start of the list.
233 ///
234 /// Inserting a progress bar that is already a member of the [`MultiProgress`]
235 /// will have no effect.
236 pub fn insert_from_back(&self, index: usize, pb: ProgressBar) -> ProgressBar {
237 self.internalize(InsertLocation::IndexFromBack(index), pb)
238 }
239
240 /// Inserts a progress bar before an existing one.
241 ///
242 /// The progress bar added will have the draw target changed to a
243 /// remote draw target that is intercepted by the multi progress
244 /// object overriding custom [`ProgressDrawTarget`] settings.
245 ///
246 /// Inserting a progress bar that is already a member of the [`MultiProgress`]
247 /// will have no effect.
248 pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar {
249 self.internalize(InsertLocation::Before(before.index().unwrap()), pb)
250 }
251
252 /// Inserts a progress bar after an existing one.
253 ///
254 /// The progress bar added will have the draw target changed to a
255 /// remote draw target that is intercepted by the multi progress
256 /// object overriding custom [`ProgressDrawTarget`] settings.
257 ///
258 /// Inserting a progress bar that is already a member of the [`MultiProgress`]
259 /// will have no effect.
260 pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar {
261 self.internalize(InsertLocation::After(after.index().unwrap()), pb)
262 }
263
264 /// Removes a progress bar.
265 ///
266 /// The progress bar is removed only if it was previously inserted or added
267 /// by the methods [`MultiProgress::insert`] or [`MultiProgress::add`].
268 /// If the passed progress bar does not satisfy the condition above,
269 /// the `remove` method does nothing.
270 pub fn remove(&self, pb: &ProgressBar) {
271 let mut state = pb.state();
272 let idx = match &state.draw_target.remote() {
273 Some((state, idx)) => {
274 // Check that this progress bar is owned by the current MultiProgress.
275 assert!(Arc::ptr_eq(&self.state, state));
276 *idx
277 }
278 _ => return,
279 };
280
281 state.draw_target = ProgressDrawTarget::hidden();
282 self.state.write().unwrap().remove_idx(idx);
283 }
284
285 fn internalize(&self, location: InsertLocation, pb: ProgressBar) -> ProgressBar {
286 let mut state = self.state.write().unwrap();
287 let idx = state.insert(location);
288 drop(state);
289
290 pb.set_draw_target(ProgressDrawTarget::new_remote(self.state.clone(), idx));
291 pb
292 }
293
294 /// Print a log line above all progress bars in the [`MultiProgress`]
295 ///
296 /// If the draw target is hidden (e.g. when standard output is not a terminal), `println()`
297 /// will not do anything.
298 pub fn println<I: AsRef<str>>(&self, msg: I) -> io::Result<()> {
299 let mut state = self.state.write().unwrap();
300 state.println(msg, Instant::now())
301 }
302
303 /// Hide all progress bars temporarily, execute `f`, then redraw the [`MultiProgress`]
304 ///
305 /// Executes 'f' even if the draw target is hidden.
306 ///
307 /// Useful for external code that writes to the standard output.
308 ///
309 /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print
310 /// anything on the progress bar will be blocked until `f` finishes.
311 /// Therefore, it is recommended to avoid long-running operations in `f`.
312 pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
313 let mut state = self.state.write().unwrap();
314 state.suspend(f, Instant::now())
315 }
316
317 pub fn clear(&self) -> io::Result<()> {
318 self.state.write().unwrap().clear(Instant::now())
319 }
320
321 pub fn is_hidden(&self) -> bool {
322 self.state.read().unwrap().draw_target.is_hidden()
323 }
324}
325
326#[derive(Debug)]
327pub(crate) struct MultiState {
328 /// The collection of states corresponding to progress bars
329 members: Vec<MultiStateMember>,
330 /// Set of removed bars, should have corresponding members in the `members` vector with a
331 /// `draw_state` of `None`.
332 free_set: Vec<usize>,
333 /// Indices to the `draw_states` to maintain correct visual order
334 ordering: Vec<usize>,
335 /// Target for draw operation for MultiProgress
336 pub(crate) draw_target: ProgressDrawTarget,
337 /// Controls how the multi progress is aligned if some of its progress bars get removed, default is `Top`
338 alignment: MultiProgressAlignment,
339 /// Lines to be drawn above everything else in the MultiProgress. These specifically come from
340 /// calling `ProgressBar::println` on a pb that is connected to a `MultiProgress`.
341 orphan_lines: Vec<LineType>,
342 /// The count of currently visible zombie lines.
343 zombie_lines_count: VisualLines,
344}
345
346impl MultiState {
347 fn new(draw_target: ProgressDrawTarget) -> Self {
348 Self {
349 members: vec![],
350 free_set: vec![],
351 ordering: vec![],
352 draw_target,
353 alignment: MultiProgressAlignment::default(),
354 orphan_lines: Vec::new(),
355 zombie_lines_count: VisualLines::default(),
356 }
357 }
358
359 pub(crate) fn mark_zombie(&mut self, index: usize) {
360 let width = self.draw_target.width().map(usize::from);
361
362 let member = &mut self.members[index];
363
364 // If the zombie is the first visual bar then we can reap it right now instead of
365 // deferring it to the next draw.
366 if index != self.ordering.first().copied().unwrap() {
367 member.is_zombie = true;
368 return;
369 }
370
371 let line_count = member
372 .draw_state
373 .as_ref()
374 .zip(width)
375 .map(|(d, width)| d.visual_line_count(.., width))
376 .unwrap_or_default();
377
378 // Track the total number of zombie lines on the screen
379 self.zombie_lines_count = self.zombie_lines_count.saturating_add(line_count);
380
381 // Make `DrawTarget` forget about the zombie lines so that they aren't cleared on next draw.
382 self.draw_target
383 .adjust_last_line_count(LineAdjust::Keep(line_count));
384
385 self.remove_idx(index);
386 }
387
388 pub(crate) fn draw(
389 &mut self,
390 mut force_draw: bool,
391 extra_lines: Option<Vec<LineType>>,
392 now: Instant,
393 ) -> io::Result<()> {
394 if panicking() {
395 return Ok(());
396 }
397
398 let width = match self.draw_target.width() {
399 Some(width) => width as usize,
400 None => return Ok(()),
401 };
402
403 // Assumption: if extra_lines is not None, then it has at least one line
404 debug_assert_eq!(
405 extra_lines.is_some(),
406 extra_lines.as_ref().map(Vec::len).unwrap_or_default() > 0
407 );
408
409 let mut reap_indices = vec![];
410
411 // Reap all consecutive 'zombie' progress bars from head of the list.
412 let mut adjust = VisualLines::default();
413 for &index in &self.ordering {
414 let member = &self.members[index];
415 if !member.is_zombie {
416 break;
417 }
418
419 let line_count = member
420 .draw_state
421 .as_ref()
422 .map(|d| d.visual_line_count(.., width))
423 .unwrap_or_default();
424 // Track the total number of zombie lines on the screen.
425 self.zombie_lines_count += line_count;
426
427 // Track the number of zombie lines that will be drawn by this call to draw.
428 adjust += line_count;
429
430 reap_indices.push(index);
431 }
432
433 // If this draw is due to a `println`, then we need to erase all the zombie lines.
434 // This is because `println` is supposed to appear above all other elements in the
435 // `MultiProgress`.
436 if extra_lines.is_some() {
437 self.draw_target
438 .adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count));
439 self.zombie_lines_count = VisualLines::default();
440 }
441
442 let orphan_visual_line_count = visual_line_count(&self.orphan_lines, width);
443 force_draw |= orphan_visual_line_count > VisualLines::default();
444 let mut drawable = match self.draw_target.drawable(force_draw, now) {
445 Some(drawable) => drawable,
446 None => return Ok(()),
447 };
448
449 let mut draw_state = drawable.state();
450 draw_state.alignment = self.alignment;
451
452 if let Some(extra_lines) = &extra_lines {
453 draw_state.lines.extend_from_slice(extra_lines.as_slice());
454 }
455
456 // Add lines from `ProgressBar::println` call.
457 draw_state.lines.append(&mut self.orphan_lines);
458
459 for index in &self.ordering {
460 let member = &self.members[*index];
461 if let Some(state) = &member.draw_state {
462 draw_state.lines.extend_from_slice(&state.lines[..]);
463 }
464 }
465
466 drop(draw_state);
467 let drawable = drawable.draw();
468
469 for index in reap_indices {
470 self.remove_idx(index);
471 }
472
473 // The zombie lines were drawn for the last time, so make `DrawTarget` forget about them
474 // so they aren't cleared on next draw.
475 if extra_lines.is_none() {
476 self.draw_target
477 .adjust_last_line_count(LineAdjust::Keep(adjust));
478 }
479
480 drawable
481 }
482
483 pub(crate) fn println<I: AsRef<str>>(&mut self, msg: I, now: Instant) -> io::Result<()> {
484 let msg = msg.as_ref();
485
486 // If msg is "", make sure a line is still printed
487 let lines: Vec<LineType> = match msg.is_empty() {
488 false => msg.lines().map(|l| LineType::Text(Into::into(l))).collect(),
489 true => vec![LineType::Empty],
490 };
491
492 self.draw(true, Some(lines), now)
493 }
494
495 pub(crate) fn draw_state(&mut self, idx: usize) -> DrawStateWrapper<'_> {
496 let member = self.members.get_mut(idx).unwrap();
497 // alignment is handled by the `MultiProgress`'s underlying draw target, so there is no
498 // point in propagating it here.
499 let state = member.draw_state.get_or_insert(DrawState::default());
500
501 DrawStateWrapper::for_multi(state, &mut self.orphan_lines)
502 }
503
504 pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, f: F, now: Instant) -> R {
505 self.clear(now).unwrap();
506 let ret = f();
507 self.draw(true, None, Instant::now()).unwrap();
508 ret
509 }
510
511 fn insert(&mut self, location: InsertLocation) -> usize {
512 let idx = if let Some(idx) = self.free_set.pop() {
513 self.members[idx] = MultiStateMember::default();
514 idx
515 } else {
516 self.members.push(MultiStateMember::default());
517 self.members.len() - 1
518 };
519
520 match location {
521 InsertLocation::End => self.ordering.push(idx),
522 InsertLocation::Index(pos) => {
523 let pos = Ord::min(pos, self.ordering.len());
524 self.ordering.insert(pos, idx);
525 }
526 InsertLocation::IndexFromBack(pos) => {
527 let pos = self.ordering.len().saturating_sub(pos);
528 self.ordering.insert(pos, idx);
529 }
530 InsertLocation::After(after_idx) => {
531 let pos = self.ordering.iter().position(|i| *i == after_idx).unwrap();
532 self.ordering.insert(pos + 1, idx);
533 }
534 InsertLocation::Before(before_idx) => {
535 let pos = self.ordering.iter().position(|i| *i == before_idx).unwrap();
536 self.ordering.insert(pos, idx);
537 }
538 }
539
540 assert_eq!(
541 self.len(),
542 self.ordering.len(),
543 "Draw state is inconsistent"
544 );
545
546 idx
547 }
548
549 fn clear(&mut self, now: Instant) -> io::Result<()> {
550 match self.draw_target.drawable(true, now) {
551 Some(mut drawable) => {
552 // Make the clear operation also wipe out zombie lines
553 drawable.adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count));
554 self.zombie_lines_count = VisualLines::default();
555 drawable.clear()
556 }
557 None => Ok(()),
558 }
559 }
560
561 fn remove_idx(&mut self, idx: usize) {
562 if self.free_set.contains(&idx) {
563 return;
564 }
565
566 self.members[idx] = MultiStateMember::default();
567 self.free_set.push(idx);
568 self.ordering.retain(|&x| x != idx);
569
570 assert_eq!(
571 self.len(),
572 self.ordering.len(),
573 "Draw state is inconsistent"
574 );
575 }
576
577 fn len(&self) -> usize {
578 self.members.len() - self.free_set.len()
579 }
580}
581
582#[derive(Default)]
583struct MultiStateMember {
584 /// Draw state will be `None` for members that haven't been drawn before, or for entries that
585 /// correspond to something in the free set.
586 draw_state: Option<DrawState>,
587 /// Whether the corresponding progress bar (more precisely, `BarState`) has been dropped.
588 is_zombie: bool,
589}
590
591impl Debug for MultiStateMember {
592 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
593 f.debug_struct("MultiStateElement")
594 .field("draw_state", &self.draw_state)
595 .field("is_zombie", &self.is_zombie)
596 .finish_non_exhaustive()
597 }
598}
599
600/// Vertical alignment of a multi progress.
601///
602/// The alignment controls how the multi progress is aligned if some of its progress bars get removed.
603/// E.g. [`Top`](MultiProgressAlignment::Top) alignment (default), when _progress bar 2_ is removed:
604/// ```ignore
605/// [0/100] progress bar 1 [0/100] progress bar 1
606/// [0/100] progress bar 2 => [0/100] progress bar 3
607/// [0/100] progress bar 3
608/// ```
609///
610/// [`Bottom`](MultiProgressAlignment::Bottom) alignment
611/// ```ignore
612/// [0/100] progress bar 1
613/// [0/100] progress bar 2 => [0/100] progress bar 1
614/// [0/100] progress bar 3 [0/100] progress bar 3
615/// ```
616#[derive(Debug, Copy, Clone, Default)]
617pub enum MultiProgressAlignment {
618 #[default]
619 Top,
620 Bottom,
621}
622
623enum InsertLocation {
624 End,
625 Index(usize),
626 IndexFromBack(usize),
627 After(usize),
628 Before(usize),
629}
630
631#[cfg(test)]
632mod tests {
633 use crate::{MultiProgress, ProgressBar, ProgressDrawTarget};
634
635 #[test]
636 fn late_pb_drop() {
637 let pb = ProgressBar::new(10);
638 let mpb = MultiProgress::new();
639 // This clone call is required to trigger a now fixed bug.
640 // See <https://github.com/console-rs/indicatif/pull/141> for context
641 #[allow(clippy::redundant_clone)]
642 mpb.add(pb.clone());
643 }
644
645 #[test]
646 fn progress_bar_sync_send() {
647 let _: Box<dyn Sync> = Box::new(ProgressBar::new(1));
648 let _: Box<dyn Send> = Box::new(ProgressBar::new(1));
649 let _: Box<dyn Sync> = Box::new(MultiProgress::new());
650 let _: Box<dyn Send> = Box::new(MultiProgress::new());
651 }
652
653 #[test]
654 fn multi_progress_hidden() {
655 let mpb = MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
656 let pb = mpb.add(ProgressBar::new(123));
657 pb.finish();
658 }
659
660 #[test]
661 fn multi_progress_modifications() {
662 let mp = MultiProgress::new();
663 let p0 = mp.add(ProgressBar::new(1));
664 let p1 = mp.add(ProgressBar::new(1));
665 let p2 = mp.add(ProgressBar::new(1));
666 let p3 = mp.add(ProgressBar::new(1));
667 mp.remove(&p2);
668 mp.remove(&p1);
669 let p4 = mp.insert(1, ProgressBar::new(1));
670
671 let state = mp.state.read().unwrap();
672 // the removed place for p1 is reused
673 assert_eq!(state.members.len(), 4);
674 assert_eq!(state.len(), 3);
675
676 // free_set may contain 1 or 2
677 match state.free_set.last() {
678 Some(1) => {
679 assert_eq!(state.ordering, vec![0, 2, 3]);
680 assert!(state.members[1].draw_state.is_none());
681 assert_eq!(p4.index().unwrap(), 2);
682 }
683 Some(2) => {
684 assert_eq!(state.ordering, vec![0, 1, 3]);
685 assert!(state.members[2].draw_state.is_none());
686 assert_eq!(p4.index().unwrap(), 1);
687 }
688 _ => unreachable!(),
689 }
690
691 assert_eq!(p0.index().unwrap(), 0);
692 assert_eq!(p1.index(), None);
693 assert_eq!(p2.index(), None);
694 assert_eq!(p3.index().unwrap(), 3);
695 }
696
697 #[test]
698 fn multi_progress_insert_from_back() {
699 let mp = MultiProgress::new();
700 let p0 = mp.add(ProgressBar::new(1));
701 let p1 = mp.add(ProgressBar::new(1));
702 let p2 = mp.add(ProgressBar::new(1));
703 let p3 = mp.insert_from_back(1, ProgressBar::new(1));
704 let p4 = mp.insert_from_back(10, ProgressBar::new(1));
705
706 let state = mp.state.read().unwrap();
707 assert_eq!(state.ordering, vec![4, 0, 1, 3, 2]);
708 assert_eq!(p0.index().unwrap(), 0);
709 assert_eq!(p1.index().unwrap(), 1);
710 assert_eq!(p2.index().unwrap(), 2);
711 assert_eq!(p3.index().unwrap(), 3);
712 assert_eq!(p4.index().unwrap(), 4);
713 }
714
715 #[test]
716 fn multi_progress_insert_after() {
717 let mp = MultiProgress::new();
718 let p0 = mp.add(ProgressBar::new(1));
719 let p1 = mp.add(ProgressBar::new(1));
720 let p2 = mp.add(ProgressBar::new(1));
721 let p3 = mp.insert_after(&p2, ProgressBar::new(1));
722 let p4 = mp.insert_after(&p0, ProgressBar::new(1));
723
724 let state = mp.state.read().unwrap();
725 assert_eq!(state.ordering, vec![0, 4, 1, 2, 3]);
726 assert_eq!(p0.index().unwrap(), 0);
727 assert_eq!(p1.index().unwrap(), 1);
728 assert_eq!(p2.index().unwrap(), 2);
729 assert_eq!(p3.index().unwrap(), 3);
730 assert_eq!(p4.index().unwrap(), 4);
731 }
732
733 #[test]
734 fn multi_progress_insert_before() {
735 let mp = MultiProgress::new();
736 let p0 = mp.add(ProgressBar::new(1));
737 let p1 = mp.add(ProgressBar::new(1));
738 let p2 = mp.add(ProgressBar::new(1));
739 let p3 = mp.insert_before(&p0, ProgressBar::new(1));
740 let p4 = mp.insert_before(&p2, ProgressBar::new(1));
741
742 let state = mp.state.read().unwrap();
743 assert_eq!(state.ordering, vec![3, 0, 1, 4, 2]);
744 assert_eq!(p0.index().unwrap(), 0);
745 assert_eq!(p1.index().unwrap(), 1);
746 assert_eq!(p2.index().unwrap(), 2);
747 assert_eq!(p3.index().unwrap(), 3);
748 assert_eq!(p4.index().unwrap(), 4);
749 }
750
751 #[test]
752 fn multi_progress_insert_before_and_after() {
753 let mp = MultiProgress::new();
754 let p0 = mp.add(ProgressBar::new(1));
755 let p1 = mp.add(ProgressBar::new(1));
756 let p2 = mp.add(ProgressBar::new(1));
757 let p3 = mp.insert_before(&p0, ProgressBar::new(1));
758 let p4 = mp.insert_after(&p3, ProgressBar::new(1));
759 let p5 = mp.insert_after(&p3, ProgressBar::new(1));
760 let p6 = mp.insert_before(&p1, ProgressBar::new(1));
761
762 let state = mp.state.read().unwrap();
763 assert_eq!(state.ordering, vec![3, 5, 4, 0, 6, 1, 2]);
764 assert_eq!(p0.index().unwrap(), 0);
765 assert_eq!(p1.index().unwrap(), 1);
766 assert_eq!(p2.index().unwrap(), 2);
767 assert_eq!(p3.index().unwrap(), 3);
768 assert_eq!(p4.index().unwrap(), 4);
769 assert_eq!(p5.index().unwrap(), 5);
770 assert_eq!(p6.index().unwrap(), 6);
771 }
772
773 #[test]
774 fn multi_progress_multiple_remove() {
775 let mp = MultiProgress::new();
776 let p0 = mp.add(ProgressBar::new(1));
777 let p1 = mp.add(ProgressBar::new(1));
778 // double remove beyond the first one have no effect
779 mp.remove(&p0);
780 mp.remove(&p0);
781 mp.remove(&p0);
782
783 let state = mp.state.read().unwrap();
784 // the removed place for p1 is reused
785 assert_eq!(state.members.len(), 2);
786 assert_eq!(state.free_set.len(), 1);
787 assert_eq!(state.len(), 1);
788 assert!(state.members[0].draw_state.is_none());
789 assert_eq!(state.free_set.last(), Some(&0));
790
791 assert_eq!(state.ordering, vec![1]);
792 assert_eq!(p0.index(), None);
793 assert_eq!(p1.index().unwrap(), 1);
794 }
795
796 #[test]
797 fn mp_no_crash_double_add() {
798 let mp = MultiProgress::new();
799 let pb = mp.add(ProgressBar::new(10));
800 mp.add(pb);
801 }
802}