Skip to main content

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}