1 | use std::fmt::{Debug, Formatter}; |
2 | use std::io; |
3 | use std::sync::{Arc, RwLock}; |
4 | use std::thread::panicking; |
5 | #[cfg (not(target_arch = "wasm32" ))] |
6 | use std::time::Instant; |
7 | |
8 | use crate::draw_target::{DrawState, DrawStateWrapper, LineAdjust, ProgressDrawTarget}; |
9 | use crate::progress_bar::ProgressBar; |
10 | #[cfg (target_arch = "wasm32" )] |
11 | use instant::Instant; |
12 | |
13 | /// Manages multiple progress bars from different threads |
14 | #[derive (Debug, Clone)] |
15 | pub struct MultiProgress { |
16 | pub(crate) state: Arc<RwLock<MultiState>>, |
17 | } |
18 | |
19 | impl Default for MultiProgress { |
20 | fn default() -> Self { |
21 | Self::with_draw_target(ProgressDrawTarget::stderr()) |
22 | } |
23 | } |
24 | |
25 | impl MultiProgress { |
26 | /// Creates a new multi progress object. |
27 | /// |
28 | /// Progress bars added to this object by default draw directly to stderr, and refresh |
29 | /// a maximum of 15 times a second. To change the refresh rate set the draw target to |
30 | /// one with a different refresh rate. |
31 | pub fn new() -> Self { |
32 | Self::default() |
33 | } |
34 | |
35 | /// Creates a new multi progress object with the given draw target. |
36 | pub fn with_draw_target(draw_target: ProgressDrawTarget) -> Self { |
37 | Self { |
38 | state: Arc::new(RwLock::new(MultiState::new(draw_target))), |
39 | } |
40 | } |
41 | |
42 | /// Sets a different draw target for the multiprogress bar. |
43 | pub fn set_draw_target(&self, target: ProgressDrawTarget) { |
44 | let mut state = self.state.write().unwrap(); |
45 | state.draw_target.disconnect(Instant::now()); |
46 | state.draw_target = target; |
47 | } |
48 | |
49 | /// Set whether we should try to move the cursor when possible instead of clearing lines. |
50 | /// |
51 | /// This can reduce flickering, but do not enable it if you intend to change the number of |
52 | /// progress bars. |
53 | pub fn set_move_cursor(&self, move_cursor: bool) { |
54 | self.state.write().unwrap().move_cursor = move_cursor; |
55 | } |
56 | |
57 | /// Set alignment flag |
58 | pub fn set_alignment(&self, alignment: MultiProgressAlignment) { |
59 | self.state.write().unwrap().alignment = alignment; |
60 | } |
61 | |
62 | /// Adds a progress bar. |
63 | /// |
64 | /// The progress bar added will have the draw target changed to a |
65 | /// remote draw target that is intercepted by the multi progress |
66 | /// object overriding custom `ProgressDrawTarget` settings. |
67 | /// |
68 | /// Adding a progress bar that is already a member of the `MultiProgress` |
69 | /// will have no effect. |
70 | pub fn add(&self, pb: ProgressBar) -> ProgressBar { |
71 | self.internalize(InsertLocation::End, pb) |
72 | } |
73 | |
74 | /// Inserts a progress bar. |
75 | /// |
76 | /// The progress bar inserted at position `index` will have the draw |
77 | /// target changed to a remote draw target that is intercepted by the |
78 | /// multi progress object overriding custom `ProgressDrawTarget` settings. |
79 | /// |
80 | /// If `index >= MultiProgressState::objects.len()`, the progress bar |
81 | /// is added to the end of the list. |
82 | /// |
83 | /// Inserting a progress bar that is already a member of the `MultiProgress` |
84 | /// will have no effect. |
85 | pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar { |
86 | self.internalize(InsertLocation::Index(index), pb) |
87 | } |
88 | |
89 | /// Inserts a progress bar from the back. |
90 | /// |
91 | /// The progress bar inserted at position `MultiProgressState::objects.len() - index` |
92 | /// will have the draw target changed to a remote draw target that is |
93 | /// intercepted by the multi progress object overriding custom |
94 | /// `ProgressDrawTarget` settings. |
95 | /// |
96 | /// If `index >= MultiProgressState::objects.len()`, the progress bar |
97 | /// is added to the start of the list. |
98 | /// |
99 | /// Inserting a progress bar that is already a member of the `MultiProgress` |
100 | /// will have no effect. |
101 | pub fn insert_from_back(&self, index: usize, pb: ProgressBar) -> ProgressBar { |
102 | self.internalize(InsertLocation::IndexFromBack(index), pb) |
103 | } |
104 | |
105 | /// Inserts a progress bar before an existing one. |
106 | /// |
107 | /// The progress bar added will have the draw target changed to a |
108 | /// remote draw target that is intercepted by the multi progress |
109 | /// object overriding custom `ProgressDrawTarget` settings. |
110 | /// |
111 | /// Inserting a progress bar that is already a member of the `MultiProgress` |
112 | /// will have no effect. |
113 | pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar { |
114 | self.internalize(InsertLocation::Before(before.index().unwrap()), pb) |
115 | } |
116 | |
117 | /// Inserts a progress bar after an existing one. |
118 | /// |
119 | /// The progress bar added will have the draw target changed to a |
120 | /// remote draw target that is intercepted by the multi progress |
121 | /// object overriding custom `ProgressDrawTarget` settings. |
122 | /// |
123 | /// Inserting a progress bar that is already a member of the `MultiProgress` |
124 | /// will have no effect. |
125 | pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar { |
126 | self.internalize(InsertLocation::After(after.index().unwrap()), pb) |
127 | } |
128 | |
129 | /// Removes a progress bar. |
130 | /// |
131 | /// The progress bar is removed only if it was previously inserted or added |
132 | /// by the methods `MultiProgress::insert` or `MultiProgress::add`. |
133 | /// If the passed progress bar does not satisfy the condition above, |
134 | /// the `remove` method does nothing. |
135 | pub fn remove(&self, pb: &ProgressBar) { |
136 | let mut state = pb.state(); |
137 | let idx = match &state.draw_target.remote() { |
138 | Some((state, idx)) => { |
139 | // Check that this progress bar is owned by the current MultiProgress. |
140 | assert!(Arc::ptr_eq(&self.state, state)); |
141 | *idx |
142 | } |
143 | _ => return, |
144 | }; |
145 | |
146 | state.draw_target = ProgressDrawTarget::hidden(); |
147 | self.state.write().unwrap().remove_idx(idx); |
148 | } |
149 | |
150 | fn internalize(&self, location: InsertLocation, pb: ProgressBar) -> ProgressBar { |
151 | let mut state = self.state.write().unwrap(); |
152 | let idx = state.insert(location); |
153 | drop(state); |
154 | |
155 | pb.set_draw_target(ProgressDrawTarget::new_remote(self.state.clone(), idx)); |
156 | pb |
157 | } |
158 | |
159 | /// Print a log line above all progress bars in the [`MultiProgress`] |
160 | /// |
161 | /// If the draw target is hidden (e.g. when standard output is not a terminal), `println()` |
162 | /// will not do anything. |
163 | pub fn println<I: AsRef<str>>(&self, msg: I) -> io::Result<()> { |
164 | let mut state = self.state.write().unwrap(); |
165 | state.println(msg, Instant::now()) |
166 | } |
167 | |
168 | /// Hide all progress bars temporarily, execute `f`, then redraw the [`MultiProgress`] |
169 | /// |
170 | /// Executes 'f' even if the draw target is hidden. |
171 | /// |
172 | /// Useful for external code that writes to the standard output. |
173 | /// |
174 | /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print |
175 | /// anything on the progress bar will be blocked until `f` finishes. |
176 | /// Therefore, it is recommended to avoid long-running operations in `f`. |
177 | pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R { |
178 | let mut state = self.state.write().unwrap(); |
179 | state.suspend(f, Instant::now()) |
180 | } |
181 | |
182 | pub fn clear(&self) -> io::Result<()> { |
183 | self.state.write().unwrap().clear(Instant::now()) |
184 | } |
185 | |
186 | pub fn is_hidden(&self) -> bool { |
187 | self.state.read().unwrap().is_hidden() |
188 | } |
189 | } |
190 | |
191 | #[derive (Debug)] |
192 | pub(crate) struct MultiState { |
193 | /// The collection of states corresponding to progress bars |
194 | members: Vec<MultiStateMember>, |
195 | /// Set of removed bars, should have corresponding members in the `members` vector with a |
196 | /// `draw_state` of `None`. |
197 | free_set: Vec<usize>, |
198 | /// Indices to the `draw_states` to maintain correct visual order |
199 | ordering: Vec<usize>, |
200 | /// Target for draw operation for MultiProgress |
201 | draw_target: ProgressDrawTarget, |
202 | /// Whether or not to just move cursor instead of clearing lines |
203 | move_cursor: bool, |
204 | /// Controls how the multi progress is aligned if some of its progress bars get removed, default is `Top` |
205 | alignment: MultiProgressAlignment, |
206 | /// Lines to be drawn above everything else in the MultiProgress. These specifically come from |
207 | /// calling `ProgressBar::println` on a pb that is connected to a `MultiProgress`. |
208 | orphan_lines: Vec<String>, |
209 | /// The count of currently visible zombie lines. |
210 | zombie_lines_count: usize, |
211 | } |
212 | |
213 | impl MultiState { |
214 | fn new(draw_target: ProgressDrawTarget) -> Self { |
215 | Self { |
216 | members: vec![], |
217 | free_set: vec![], |
218 | ordering: vec![], |
219 | draw_target, |
220 | move_cursor: false, |
221 | alignment: MultiProgressAlignment::default(), |
222 | orphan_lines: Vec::new(), |
223 | zombie_lines_count: 0, |
224 | } |
225 | } |
226 | |
227 | pub(crate) fn mark_zombie(&mut self, index: usize) { |
228 | let member = &mut self.members[index]; |
229 | |
230 | // If the zombie is the first visual bar then we can reap it right now instead of |
231 | // deferring it to the next draw. |
232 | if index != self.ordering.first().copied().unwrap() { |
233 | member.is_zombie = true; |
234 | return; |
235 | } |
236 | |
237 | let line_count = member |
238 | .draw_state |
239 | .as_ref() |
240 | .map(|d| d.lines.len()) |
241 | .unwrap_or_default(); |
242 | |
243 | // Track the total number of zombie lines on the screen |
244 | self.zombie_lines_count += line_count; |
245 | |
246 | // Make `DrawTarget` forget about the zombie lines so that they aren't cleared on next draw. |
247 | self.draw_target |
248 | .adjust_last_line_count(LineAdjust::Keep(line_count)); |
249 | |
250 | self.remove_idx(index); |
251 | } |
252 | |
253 | pub(crate) fn draw( |
254 | &mut self, |
255 | mut force_draw: bool, |
256 | extra_lines: Option<Vec<String>>, |
257 | now: Instant, |
258 | ) -> io::Result<()> { |
259 | if panicking() { |
260 | return Ok(()); |
261 | } |
262 | let width = self.width() as f64; |
263 | // Calculate real length based on terminal width |
264 | // This take in account linewrap from terminal |
265 | fn real_len(lines: &[String], width: f64) -> usize { |
266 | lines.iter().fold(0, |sum, val| { |
267 | sum + (console::measure_text_width(val) as f64 / width).ceil() as usize |
268 | }) |
269 | } |
270 | |
271 | // Assumption: if extra_lines is not None, then it has at least one line |
272 | debug_assert_eq!( |
273 | extra_lines.is_some(), |
274 | extra_lines.as_ref().map(Vec::len).unwrap_or_default() > 0 |
275 | ); |
276 | |
277 | let mut reap_indices = vec![]; |
278 | |
279 | // Reap all consecutive 'zombie' progress bars from head of the list. |
280 | let mut adjust = 0; |
281 | for &index in &self.ordering { |
282 | let member = &self.members[index]; |
283 | if !member.is_zombie { |
284 | break; |
285 | } |
286 | |
287 | let line_count = member |
288 | .draw_state |
289 | .as_ref() |
290 | .map(|d| real_len(&d.lines, width)) |
291 | .unwrap_or_default(); |
292 | // Track the total number of zombie lines on the screen. |
293 | self.zombie_lines_count += line_count; |
294 | |
295 | // Track the number of zombie lines that will be drawn by this call to draw. |
296 | adjust += line_count; |
297 | |
298 | reap_indices.push(index); |
299 | } |
300 | |
301 | // If this draw is due to a `println`, then we need to erase all the zombie lines. |
302 | // This is because `println` is supposed to appear above all other elements in the |
303 | // `MultiProgress`. |
304 | if extra_lines.is_some() { |
305 | self.draw_target |
306 | .adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count)); |
307 | self.zombie_lines_count = 0; |
308 | } |
309 | |
310 | let orphan_lines_count = real_len(&self.orphan_lines, width); |
311 | force_draw |= orphan_lines_count > 0; |
312 | let mut drawable = match self.draw_target.drawable(force_draw, now) { |
313 | Some(drawable) => drawable, |
314 | None => return Ok(()), |
315 | }; |
316 | |
317 | let mut draw_state = drawable.state(); |
318 | draw_state.orphan_lines_count = orphan_lines_count; |
319 | draw_state.alignment = self.alignment; |
320 | |
321 | if let Some(extra_lines) = &extra_lines { |
322 | draw_state.lines.extend_from_slice(extra_lines.as_slice()); |
323 | draw_state.orphan_lines_count += real_len(extra_lines, width); |
324 | } |
325 | |
326 | // Add lines from `ProgressBar::println` call. |
327 | draw_state.lines.append(&mut self.orphan_lines); |
328 | |
329 | for index in &self.ordering { |
330 | let member = &self.members[*index]; |
331 | if let Some(state) = &member.draw_state { |
332 | draw_state.lines.extend_from_slice(&state.lines[..]); |
333 | } |
334 | } |
335 | |
336 | drop(draw_state); |
337 | let drawable = drawable.draw(); |
338 | |
339 | for index in reap_indices { |
340 | self.remove_idx(index); |
341 | } |
342 | |
343 | // The zombie lines were drawn for the last time, so make `DrawTarget` forget about them |
344 | // so they aren't cleared on next draw. |
345 | if extra_lines.is_none() { |
346 | self.draw_target |
347 | .adjust_last_line_count(LineAdjust::Keep(adjust)); |
348 | } |
349 | |
350 | drawable |
351 | } |
352 | |
353 | pub(crate) fn println<I: AsRef<str>>(&mut self, msg: I, now: Instant) -> io::Result<()> { |
354 | let msg = msg.as_ref(); |
355 | |
356 | // If msg is "", make sure a line is still printed |
357 | let lines: Vec<String> = match msg.is_empty() { |
358 | false => msg.lines().map(Into::into).collect(), |
359 | true => vec![String::new()], |
360 | }; |
361 | |
362 | self.draw(true, Some(lines), now) |
363 | } |
364 | |
365 | pub(crate) fn draw_state(&mut self, idx: usize) -> DrawStateWrapper<'_> { |
366 | let member = self.members.get_mut(idx).unwrap(); |
367 | // alignment is handled by the `MultiProgress`'s underlying draw target, so there is no |
368 | // point in propagating it here. |
369 | let state = member.draw_state.get_or_insert(DrawState { |
370 | move_cursor: self.move_cursor, |
371 | ..Default::default() |
372 | }); |
373 | |
374 | DrawStateWrapper::for_multi(state, &mut self.orphan_lines) |
375 | } |
376 | |
377 | pub(crate) fn is_hidden(&self) -> bool { |
378 | self.draw_target.is_hidden() |
379 | } |
380 | |
381 | pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, f: F, now: Instant) -> R { |
382 | self.clear(now).unwrap(); |
383 | let ret = f(); |
384 | self.draw(true, None, Instant::now()).unwrap(); |
385 | ret |
386 | } |
387 | |
388 | pub(crate) fn width(&self) -> u16 { |
389 | self.draw_target.width() |
390 | } |
391 | |
392 | fn insert(&mut self, location: InsertLocation) -> usize { |
393 | let idx = if let Some(idx) = self.free_set.pop() { |
394 | self.members[idx] = MultiStateMember::default(); |
395 | idx |
396 | } else { |
397 | self.members.push(MultiStateMember::default()); |
398 | self.members.len() - 1 |
399 | }; |
400 | |
401 | match location { |
402 | InsertLocation::End => self.ordering.push(idx), |
403 | InsertLocation::Index(pos) => { |
404 | let pos = Ord::min(pos, self.ordering.len()); |
405 | self.ordering.insert(pos, idx); |
406 | } |
407 | InsertLocation::IndexFromBack(pos) => { |
408 | let pos = self.ordering.len().saturating_sub(pos); |
409 | self.ordering.insert(pos, idx); |
410 | } |
411 | InsertLocation::After(after_idx) => { |
412 | let pos = self.ordering.iter().position(|i| *i == after_idx).unwrap(); |
413 | self.ordering.insert(pos + 1, idx); |
414 | } |
415 | InsertLocation::Before(before_idx) => { |
416 | let pos = self.ordering.iter().position(|i| *i == before_idx).unwrap(); |
417 | self.ordering.insert(pos, idx); |
418 | } |
419 | } |
420 | |
421 | assert_eq!( |
422 | self.len(), |
423 | self.ordering.len(), |
424 | "Draw state is inconsistent" |
425 | ); |
426 | |
427 | idx |
428 | } |
429 | |
430 | fn clear(&mut self, now: Instant) -> io::Result<()> { |
431 | match self.draw_target.drawable(true, now) { |
432 | Some(mut drawable) => { |
433 | // Make the clear operation also wipe out zombie lines |
434 | drawable.adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count)); |
435 | self.zombie_lines_count = 0; |
436 | drawable.clear() |
437 | } |
438 | None => Ok(()), |
439 | } |
440 | } |
441 | |
442 | fn remove_idx(&mut self, idx: usize) { |
443 | if self.free_set.contains(&idx) { |
444 | return; |
445 | } |
446 | |
447 | self.members[idx] = MultiStateMember::default(); |
448 | self.free_set.push(idx); |
449 | self.ordering.retain(|&x| x != idx); |
450 | |
451 | assert_eq!( |
452 | self.len(), |
453 | self.ordering.len(), |
454 | "Draw state is inconsistent" |
455 | ); |
456 | } |
457 | |
458 | fn len(&self) -> usize { |
459 | self.members.len() - self.free_set.len() |
460 | } |
461 | } |
462 | |
463 | #[derive (Default)] |
464 | struct MultiStateMember { |
465 | /// Draw state will be `None` for members that haven't been drawn before, or for entries that |
466 | /// correspond to something in the free set. |
467 | draw_state: Option<DrawState>, |
468 | /// Whether the corresponding progress bar (more precisely, `BarState`) has been dropped. |
469 | is_zombie: bool, |
470 | } |
471 | |
472 | impl Debug for MultiStateMember { |
473 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
474 | f&mut DebugStruct<'_, '_>.debug_struct("MultiStateElement" ) |
475 | .field("draw_state" , &self.draw_state) |
476 | .field(name:"is_zombie" , &self.is_zombie) |
477 | .finish_non_exhaustive() |
478 | } |
479 | } |
480 | |
481 | /// Vertical alignment of a multi progress. |
482 | /// |
483 | /// The alignment controls how the multi progress is aligned if some of its progress bars get removed. |
484 | /// E.g. `Top` alignment (default), when _progress bar 2_ is removed: |
485 | /// ```ignore |
486 | /// [0/100] progress bar 1 [0/100] progress bar 1 |
487 | /// [0/100] progress bar 2 => [0/100] progress bar 3 |
488 | /// [0/100] progress bar 3 |
489 | /// ``` |
490 | /// |
491 | /// `Bottom` alignment |
492 | /// ```ignore |
493 | /// [0/100] progress bar 1 |
494 | /// [0/100] progress bar 2 => [0/100] progress bar 1 |
495 | /// [0/100] progress bar 3 [0/100] progress bar 3 |
496 | /// ``` |
497 | #[derive (Debug, Copy, Clone)] |
498 | pub enum MultiProgressAlignment { |
499 | Top, |
500 | Bottom, |
501 | } |
502 | |
503 | impl Default for MultiProgressAlignment { |
504 | fn default() -> Self { |
505 | Self::Top |
506 | } |
507 | } |
508 | |
509 | enum InsertLocation { |
510 | End, |
511 | Index(usize), |
512 | IndexFromBack(usize), |
513 | After(usize), |
514 | Before(usize), |
515 | } |
516 | |
517 | #[cfg (test)] |
518 | mod tests { |
519 | use crate::{MultiProgress, ProgressBar, ProgressDrawTarget}; |
520 | |
521 | #[test ] |
522 | fn late_pb_drop() { |
523 | let pb = ProgressBar::new(10); |
524 | let mpb = MultiProgress::new(); |
525 | // This clone call is required to trigger a now fixed bug. |
526 | // See <https://github.com/console-rs/indicatif/pull/141> for context |
527 | #[allow (clippy::redundant_clone)] |
528 | mpb.add(pb.clone()); |
529 | } |
530 | |
531 | #[test ] |
532 | fn progress_bar_sync_send() { |
533 | let _: Box<dyn Sync> = Box::new(ProgressBar::new(1)); |
534 | let _: Box<dyn Send> = Box::new(ProgressBar::new(1)); |
535 | let _: Box<dyn Sync> = Box::new(MultiProgress::new()); |
536 | let _: Box<dyn Send> = Box::new(MultiProgress::new()); |
537 | } |
538 | |
539 | #[test ] |
540 | fn multi_progress_hidden() { |
541 | let mpb = MultiProgress::with_draw_target(ProgressDrawTarget::hidden()); |
542 | let pb = mpb.add(ProgressBar::new(123)); |
543 | pb.finish(); |
544 | } |
545 | |
546 | #[test ] |
547 | fn multi_progress_modifications() { |
548 | let mp = MultiProgress::new(); |
549 | let p0 = mp.add(ProgressBar::new(1)); |
550 | let p1 = mp.add(ProgressBar::new(1)); |
551 | let p2 = mp.add(ProgressBar::new(1)); |
552 | let p3 = mp.add(ProgressBar::new(1)); |
553 | mp.remove(&p2); |
554 | mp.remove(&p1); |
555 | let p4 = mp.insert(1, ProgressBar::new(1)); |
556 | |
557 | let state = mp.state.read().unwrap(); |
558 | // the removed place for p1 is reused |
559 | assert_eq!(state.members.len(), 4); |
560 | assert_eq!(state.len(), 3); |
561 | |
562 | // free_set may contain 1 or 2 |
563 | match state.free_set.last() { |
564 | Some(1) => { |
565 | assert_eq!(state.ordering, vec![0, 2, 3]); |
566 | assert!(state.members[1].draw_state.is_none()); |
567 | assert_eq!(p4.index().unwrap(), 2); |
568 | } |
569 | Some(2) => { |
570 | assert_eq!(state.ordering, vec![0, 1, 3]); |
571 | assert!(state.members[2].draw_state.is_none()); |
572 | assert_eq!(p4.index().unwrap(), 1); |
573 | } |
574 | _ => unreachable!(), |
575 | } |
576 | |
577 | assert_eq!(p0.index().unwrap(), 0); |
578 | assert_eq!(p1.index(), None); |
579 | assert_eq!(p2.index(), None); |
580 | assert_eq!(p3.index().unwrap(), 3); |
581 | } |
582 | |
583 | #[test ] |
584 | fn multi_progress_insert_from_back() { |
585 | let mp = MultiProgress::new(); |
586 | let p0 = mp.add(ProgressBar::new(1)); |
587 | let p1 = mp.add(ProgressBar::new(1)); |
588 | let p2 = mp.add(ProgressBar::new(1)); |
589 | let p3 = mp.insert_from_back(1, ProgressBar::new(1)); |
590 | let p4 = mp.insert_from_back(10, ProgressBar::new(1)); |
591 | |
592 | let state = mp.state.read().unwrap(); |
593 | assert_eq!(state.ordering, vec![4, 0, 1, 3, 2]); |
594 | assert_eq!(p0.index().unwrap(), 0); |
595 | assert_eq!(p1.index().unwrap(), 1); |
596 | assert_eq!(p2.index().unwrap(), 2); |
597 | assert_eq!(p3.index().unwrap(), 3); |
598 | assert_eq!(p4.index().unwrap(), 4); |
599 | } |
600 | |
601 | #[test ] |
602 | fn multi_progress_insert_after() { |
603 | let mp = MultiProgress::new(); |
604 | let p0 = mp.add(ProgressBar::new(1)); |
605 | let p1 = mp.add(ProgressBar::new(1)); |
606 | let p2 = mp.add(ProgressBar::new(1)); |
607 | let p3 = mp.insert_after(&p2, ProgressBar::new(1)); |
608 | let p4 = mp.insert_after(&p0, ProgressBar::new(1)); |
609 | |
610 | let state = mp.state.read().unwrap(); |
611 | assert_eq!(state.ordering, vec![0, 4, 1, 2, 3]); |
612 | assert_eq!(p0.index().unwrap(), 0); |
613 | assert_eq!(p1.index().unwrap(), 1); |
614 | assert_eq!(p2.index().unwrap(), 2); |
615 | assert_eq!(p3.index().unwrap(), 3); |
616 | assert_eq!(p4.index().unwrap(), 4); |
617 | } |
618 | |
619 | #[test ] |
620 | fn multi_progress_insert_before() { |
621 | let mp = MultiProgress::new(); |
622 | let p0 = mp.add(ProgressBar::new(1)); |
623 | let p1 = mp.add(ProgressBar::new(1)); |
624 | let p2 = mp.add(ProgressBar::new(1)); |
625 | let p3 = mp.insert_before(&p0, ProgressBar::new(1)); |
626 | let p4 = mp.insert_before(&p2, ProgressBar::new(1)); |
627 | |
628 | let state = mp.state.read().unwrap(); |
629 | assert_eq!(state.ordering, vec![3, 0, 1, 4, 2]); |
630 | assert_eq!(p0.index().unwrap(), 0); |
631 | assert_eq!(p1.index().unwrap(), 1); |
632 | assert_eq!(p2.index().unwrap(), 2); |
633 | assert_eq!(p3.index().unwrap(), 3); |
634 | assert_eq!(p4.index().unwrap(), 4); |
635 | } |
636 | |
637 | #[test ] |
638 | fn multi_progress_insert_before_and_after() { |
639 | let mp = MultiProgress::new(); |
640 | let p0 = mp.add(ProgressBar::new(1)); |
641 | let p1 = mp.add(ProgressBar::new(1)); |
642 | let p2 = mp.add(ProgressBar::new(1)); |
643 | let p3 = mp.insert_before(&p0, ProgressBar::new(1)); |
644 | let p4 = mp.insert_after(&p3, ProgressBar::new(1)); |
645 | let p5 = mp.insert_after(&p3, ProgressBar::new(1)); |
646 | let p6 = mp.insert_before(&p1, ProgressBar::new(1)); |
647 | |
648 | let state = mp.state.read().unwrap(); |
649 | assert_eq!(state.ordering, vec![3, 5, 4, 0, 6, 1, 2]); |
650 | assert_eq!(p0.index().unwrap(), 0); |
651 | assert_eq!(p1.index().unwrap(), 1); |
652 | assert_eq!(p2.index().unwrap(), 2); |
653 | assert_eq!(p3.index().unwrap(), 3); |
654 | assert_eq!(p4.index().unwrap(), 4); |
655 | assert_eq!(p5.index().unwrap(), 5); |
656 | assert_eq!(p6.index().unwrap(), 6); |
657 | } |
658 | |
659 | #[test ] |
660 | fn multi_progress_multiple_remove() { |
661 | let mp = MultiProgress::new(); |
662 | let p0 = mp.add(ProgressBar::new(1)); |
663 | let p1 = mp.add(ProgressBar::new(1)); |
664 | // double remove beyond the first one have no effect |
665 | mp.remove(&p0); |
666 | mp.remove(&p0); |
667 | mp.remove(&p0); |
668 | |
669 | let state = mp.state.read().unwrap(); |
670 | // the removed place for p1 is reused |
671 | assert_eq!(state.members.len(), 2); |
672 | assert_eq!(state.free_set.len(), 1); |
673 | assert_eq!(state.len(), 1); |
674 | assert!(state.members[0].draw_state.is_none()); |
675 | assert_eq!(state.free_set.last(), Some(&0)); |
676 | |
677 | assert_eq!(state.ordering, vec![1]); |
678 | assert_eq!(p0.index(), None); |
679 | assert_eq!(p1.index().unwrap(), 1); |
680 | } |
681 | |
682 | #[test ] |
683 | fn mp_no_crash_double_add() { |
684 | let mp = MultiProgress::new(); |
685 | let pb = mp.add(ProgressBar::new(10)); |
686 | mp.add(pb); |
687 | } |
688 | } |
689 | |