Skip to main content

ghostscope_ui/components/loading/
progress.rs

1use 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
10/// Progress bar component for loading
11pub struct ProgressRenderer;
12
13impl ProgressRenderer {
14    /// Render overall progress bar
15    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    /// Render recently loaded modules list
37    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                _ => {} // Skip queued and loading states
87            }
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    /// Render current loading status
114    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!("...{}", &current[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    /// Render total statistics
141    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}