ghostscope_ui/components/loading/
progress.rs1use ratatui::{
2 style::{Color, Style},
3 text::{Line, Span},
4 widgets::{Block, Borders, Gauge, Paragraph},
5 Frame,
6};
7
8use super::{DebugSourceCounts, LoadingProgress, ModuleState};
9
10pub struct ProgressRenderer;
12
13impl ProgressRenderer {
14 pub fn render_progress_bar(
16 f: &mut Frame,
17 area: ratatui::layout::Rect,
18 progress: &LoadingProgress,
19 ) {
20 let ratio = progress.progress_ratio();
21 let completed = progress.completed_count;
22 let total = progress.total_modules();
23
24 let progress_bar = Gauge::default()
25 .block(Block::default().borders(Borders::NONE))
26 .gauge_style(Style::default().fg(Color::Cyan))
27 .ratio(ratio)
28 .label(format!(
29 "{completed}/{total} modules ({}%)",
30 (ratio * 100.0) as u8
31 ));
32
33 f.render_widget(progress_bar, area);
34 }
35
36 pub fn render_recent_modules(
38 f: &mut Frame,
39 area: ratatui::layout::Rect,
40 progress: &LoadingProgress,
41 max_items: usize,
42 ) {
43 let recent = progress.recently_finished(max_items);
44 let mut lines = Vec::new();
45
46 for module in recent {
47 let path = if module.path.len() > 50 {
48 format!("...{}", &module.path[module.path.len() - 47..])
49 } else {
50 module.path.clone()
51 };
52
53 match &module.state {
54 ModuleState::Completed => {
55 if let Some(stats) = &module.stats {
56 let load_time = module.load_time.unwrap_or(0.0);
57 let mut spans = vec![
58 Span::styled("✅ ", Style::default().fg(Color::Green)),
59 Span::styled(path, Style::default().fg(Color::White)),
60 Span::styled(" debug: ", Style::default().fg(Color::Gray)),
61 ];
62 push_module_debug_source_spans(&mut spans, stats);
63 spans.push(Span::styled(
64 format!(
65 " | Functions: {} | Variables: {} | Types: {} | Time: {:.1}s",
66 stats.functions, stats.variables, stats.types, load_time
67 ),
68 Style::default().fg(Color::Gray),
69 ));
70 let line = Line::from(spans);
71 lines.push(line);
72 }
73 }
74 ModuleState::Failed(error) => {
75 let load_time = module.load_time.unwrap_or(0.0);
76 let line = Line::from(vec![
77 Span::styled("✗ ", Style::default().fg(Color::Red)),
78 Span::styled(path, Style::default().fg(Color::White)),
79 Span::styled(
80 format!(" ❌ Failed: {error} | Time: {load_time:.1}s"),
81 Style::default().fg(Color::Red),
82 ),
83 ]);
84 lines.push(line);
85 }
86 _ => {} }
88 }
89
90 if lines.is_empty() {
91 lines.push(Line::from(Span::styled(
92 "No modules processed yet...",
93 Style::default().fg(Color::DarkGray),
94 )));
95 }
96
97 let title = if progress.failed_count > 0 {
98 format!("📁 Recently processed: ({} failed)", progress.failed_count)
99 } else {
100 "📁 Recently loaded:".to_string()
101 };
102
103 let paragraph = Paragraph::new(lines).block(
104 Block::default()
105 .title(title)
106 .borders(Borders::NONE)
107 .title_style(Style::default().fg(Color::Yellow)),
108 );
109
110 f.render_widget(paragraph, area);
111 }
112
113 pub fn render_current_status(
115 f: &mut Frame,
116 area: ratatui::layout::Rect,
117 progress: &LoadingProgress,
118 ) {
119 let status_line = if let Some(current) = &progress.current_loading {
120 let path = if current.len() > 60 {
121 format!("...{}", ¤t[current.len() - 57..])
122 } else {
123 current.clone()
124 };
125 Line::from(vec![
126 Span::styled("⏳ Loading: ", Style::default().fg(Color::Yellow)),
127 Span::styled(path, Style::default().fg(Color::White)),
128 ])
129 } else {
130 Line::from(Span::styled(
131 "Waiting for next module...",
132 Style::default().fg(Color::DarkGray),
133 ))
134 };
135
136 let paragraph = Paragraph::new(status_line);
137 f.render_widget(paragraph, area);
138 }
139
140 pub fn render_stats(f: &mut Frame, area: ratatui::layout::Rect, progress: &LoadingProgress) {
142 let stats = progress.total_stats();
143 let elapsed = progress.elapsed_time();
144 let debug_sources = progress.debug_sources.summary();
145
146 let mut spans = vec![
147 Span::styled("⏱️ Elapsed: ", Style::default().fg(Color::DarkGray)),
148 Span::styled(format!("{elapsed:.1}s"), Style::default().fg(Color::White)),
149 Span::styled(" | ", Style::default().fg(Color::DarkGray)),
150 Span::styled("📊 Total: ", Style::default().fg(Color::DarkGray)),
151 Span::styled(
152 format!(
153 "{} functions | {} variables | {} types",
154 stats.functions, stats.variables, stats.types
155 ),
156 Style::default().fg(Color::Cyan),
157 ),
158 ];
159 if !debug_sources.is_empty() {
160 spans.push(Span::styled(" | ", Style::default().fg(Color::DarkGray)));
161 spans.push(Span::styled(
162 "Debug: ",
163 Style::default().fg(Color::DarkGray),
164 ));
165 push_debug_source_count_spans(&mut spans, &progress.debug_sources);
166 }
167
168 let stats_line = Line::from(spans);
169
170 let paragraph = Paragraph::new(stats_line);
171 f.render_widget(paragraph, area);
172 }
173}
174
175fn push_module_debug_source_spans(
176 spans: &mut Vec<Span<'static>>,
177 stats: &crate::components::loading::ModuleStats,
178) {
179 spans.push(Span::styled(
180 stats.debug_source.clone(),
181 debug_source_style(&stats.debug_source),
182 ));
183
184 if let Some(path) = stats.debug_source_path.as_deref() {
185 let file = std::path::Path::new(path)
186 .file_name()
187 .and_then(|name| name.to_str())
188 .unwrap_or(path);
189 spans.push(Span::styled(
190 format!(" {file}"),
191 Style::default().fg(Color::Gray),
192 ));
193 }
194}
195
196pub(crate) fn push_debug_source_count_spans(
197 spans: &mut Vec<Span<'static>>,
198 counts: &DebugSourceCounts,
199) {
200 let mut first = true;
201 push_debug_source_count(spans, &mut first, "embedded", counts.embedded);
202 push_debug_source_count(spans, &mut first, "explicit", counts.explicit);
203 push_debug_source_count(spans, &mut first, "debuglink", counts.debuglink);
204 push_debug_source_count(spans, &mut first, "debuginfod", counts.debuginfod);
205 push_debug_source_count(spans, &mut first, "missing", counts.missing);
206 push_debug_source_count(spans, &mut first, "other", counts.other);
207}
208
209fn push_debug_source_count(
210 spans: &mut Vec<Span<'static>>,
211 first: &mut bool,
212 label: &str,
213 count: usize,
214) {
215 if count == 0 {
216 return;
217 }
218
219 if !*first {
220 spans.push(Span::styled(" ", Style::default().fg(Color::DarkGray)));
221 }
222 *first = false;
223
224 spans.push(Span::styled(
225 format!("{label}:{count}"),
226 debug_source_style(label),
227 ));
228}
229
230pub(crate) fn debug_source_style(source: &str) -> Style {
231 Style::default().fg(match source {
232 "embedded" => Color::Green,
233 "explicit" => Color::Cyan,
234 "debuglink" => Color::Blue,
235 "debuginfod" => Color::Magenta,
236 "missing" => Color::Yellow,
237 _ => Color::DarkGray,
238 })
239}