cliclack/
multiprogress.rs1use std::{
2 fmt::Display,
3 sync::{
4 atomic::{AtomicUsize, Ordering},
5 Arc, RwLock,
6 },
7};
8
9use console::Term;
10
11use crate::{progress::ProgressBar, theme::THEME, ThemeState};
12
13const HEADER_HEIGHT: usize = 1;
14
15#[derive(Clone)]
17pub struct MultiProgress {
18 multi: indicatif::MultiProgress,
19 bars: Arc<RwLock<Vec<ProgressBar>>>,
20 prompt: String,
21 logs: Arc<AtomicUsize>,
22}
23
24impl MultiProgress {
25 pub fn new(prompt: impl Display) -> Self {
27 let theme = THEME.read().unwrap();
28 let multi = indicatif::MultiProgress::new();
29
30 let header =
31 theme.format_header(&ThemeState::Active, (prompt.to_string() + "\n ").trim_end());
32
33 multi.println(header).ok();
34
35 Self {
36 multi,
37 bars: Default::default(),
38 prompt: prompt.to_string(),
39 logs: Default::default(),
40 }
41 }
42
43 pub fn add(&self, pb: ProgressBar) -> ProgressBar {
47 let bars_count = self.length();
48 self.insert(bars_count, pb)
49 }
50
51 pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar {
55 let bars_count = self.length();
56 let index = index.min(bars_count);
57 if index == bars_count {
58 for bar in self.bars.write().unwrap().iter_mut() {
60 bar.options_write().last = false;
61 bar.redraw_active();
62 }
63 }
64 let ProgressBar { bar, options } = pb;
66 let bar = self.multi.insert(index, bar);
67 {
68 let mut options = options.write().unwrap();
69 options.grouped = true;
70 if index == bars_count {
71 options.last = true;
72 }
73 }
74
75 let pb = ProgressBar { bar, options };
76 self.bars.write().unwrap().insert(index, pb.clone());
77 pb
78 }
79
80 pub fn length(&self) -> usize {
82 self.bars.read().unwrap().len()
83 }
84
85 pub fn println(&self, message: impl Display) {
91 let theme = THEME.read().unwrap();
92 let symbol = theme.remark_symbol();
93 let log = theme.format_log_with_spacing(&message.to_string(), &symbol, false);
94 self.logs.fetch_add(log.lines().count(), Ordering::SeqCst);
95 self.multi.println(log).ok();
96 }
97
98 pub fn stop(&self) {
100 self.stop_with(&ThemeState::Submit)
101 }
102
103 pub fn cancel(&self) {
105 self.stop_with(&ThemeState::Cancel)
106 }
107
108 pub fn error(&self, error: impl Display) {
110 self.stop_with(&ThemeState::Error(error.to_string()))
111 }
112
113 fn stop_with(&self, state: &ThemeState) {
114 let mut inner_height = self.logs.load(Ordering::SeqCst);
115
116 for pb in self.bars.read().unwrap().iter() {
118 inner_height += pb.redraw_finished(pb.bar.message(), state);
121 pb.bar.finish_and_clear();
122 }
123
124 let term = Term::stderr();
125
126 term.move_cursor_up(inner_height).ok();
128 term.clear_last_lines(HEADER_HEIGHT).ok();
129 term.write_str(
130 &THEME
131 .read()
132 .unwrap()
133 .format_header(state, (self.prompt.clone() + "\n ").trim_end()),
134 )
135 .ok();
136 term.move_cursor_down(inner_height).ok();
137 }
138}