git_plumber/tui/widget/pack_idx_details/formatters/
objects.rs1use crate::git::pack::PackIndex;
2use ratatui::style::{Color, Modifier, Style};
3use ratatui::text::Line;
4
5pub struct ObjectsFormatter<'a> {
6 pack_index: &'a PackIndex,
7}
8
9impl<'a> ObjectsFormatter<'a> {
10 pub fn new(pack_index: &'a PackIndex) -> Self {
11 Self { pack_index }
12 }
13
14 fn calculate_object_byte_position(&self, object_index: usize) -> u64 {
26 let header_size = 8u64; let fanout_size = 256 * 4u64; let object_names_start = header_size + fanout_size;
29
30 object_names_start + (object_index as u64 * 20) + 1
33 }
34
35 fn calculate_crc32_byte_position(&self, object_index: usize) -> u64 {
37 let header_size = 8u64; let fanout_size = 256 * 4u64; let object_names_size = self.pack_index.object_count() as u64 * 20; let crc32_start = header_size + fanout_size + object_names_size;
41
42 crc32_start + (object_index as u64 * 4) + 1
45 }
46
47 fn calculate_offset_byte_position(&self, object_index: usize) -> u64 {
49 let header_size = 8u64; let fanout_size = 256 * 4u64; let object_names_size = self.pack_index.object_count() as u64 * 20; let crc32_size = self.pack_index.object_count() as u64 * 4; let offsets_start = header_size + fanout_size + object_names_size + crc32_size;
54
55 offsets_start + (object_index as u64 * 4) + 1
58 }
59
60 pub fn format_objects_overview(&self, lines: &mut Vec<Line<'static>>) {
61 self.format_object_names_table(lines);
62 self.format_crc32_table(lines);
63 self.format_offsets_table(lines);
64 }
65
66 fn format_object_names_table(&self, lines: &mut Vec<Line<'static>>) {
67 lines.push(Line::styled(
68 "OBJECT NAMES TABLE",
69 Style::default().add_modifier(Modifier::BOLD),
70 ));
71 lines.push(Line::from("─".repeat(25)));
72 lines.push(Line::from(""));
73
74 lines.push(Line::from(
75 "SHA-1 hashes sorted lexicographically (20 bytes each)",
76 ));
77 lines.push(Line::from(format!(
78 "Total objects: {}",
79 self.pack_index.object_count()
80 )));
81 lines.push(Line::from(""));
82
83 if self.pack_index.object_names.is_empty() {
84 lines.push(Line::from("No objects in this index."));
85 lines.push(Line::from(""));
86 return;
87 }
88
89 lines.push(Line::from(
91 "┌──────┬──────────────────────────────────────────┐",
92 ));
93 lines.push(Line::styled(
94 "│ Byte │ SHA-1 Hash │",
95 Style::default().add_modifier(Modifier::BOLD),
96 ));
97 lines.push(Line::from(
98 "├──────┼──────────────────────────────────────────┤",
99 ));
100
101 let entries_to_show = self.determine_sample_indices();
102
103 for (i, show_entry) in entries_to_show.iter().enumerate() {
104 if *show_entry {
105 let hash = hex::encode(self.pack_index.object_names[i]);
106 let byte_pos = self.calculate_object_byte_position(i);
107
108 let line_parts = vec![
109 ("│ ".to_string(), Style::default()),
110 (format!("{:4}", byte_pos), Style::default().fg(Color::Cyan)),
111 (" │ ".to_string(), Style::default()),
112 (hash, Style::default().fg(Color::Yellow)),
113 (" │".to_string(), Style::default()),
114 ];
115
116 let mut line = Line::default();
117 for (text, style) in line_parts {
118 line.spans.push(ratatui::text::Span::styled(text, style));
119 }
120 lines.push(line);
121 } else if i > 0 && entries_to_show[i - 1] {
122 let line_parts = vec![
124 ("│ ".to_string(), Style::default()),
125 ("...".to_string(), Style::default().fg(Color::Gray)),
126 (" │ ".to_string(), Style::default()),
127 (
128 "... ".to_string(),
129 Style::default().fg(Color::Gray),
130 ),
131 (" │".to_string(), Style::default()),
132 ];
133
134 let mut line = Line::default();
135 for (text, style) in line_parts {
136 line.spans.push(ratatui::text::Span::styled(text, style));
137 }
138 lines.push(line);
139 }
140 }
141
142 lines.push(Line::from(
143 "└──────┴──────────────────────────────────────────┘",
144 ));
145 lines.push(Line::from(""));
146 }
147
148 fn format_crc32_table(&self, lines: &mut Vec<Line<'static>>) {
149 lines.push(Line::styled(
150 "CRC32 CHECKSUMS TABLE",
151 Style::default().add_modifier(Modifier::BOLD),
152 ));
153 lines.push(Line::from("─".repeat(30)));
154 lines.push(Line::from(""));
155
156 lines.push(Line::from(
157 "CRC32 checksums for integrity verification (4 bytes each)",
158 ));
159 lines.push(Line::from(format!(
160 "Total checksums: {}",
161 self.pack_index.crc32_checksums.len()
162 )));
163 lines.push(Line::from(""));
164
165 if self.pack_index.crc32_checksums.is_empty() {
166 lines.push(Line::from("No CRC32 checksums available."));
167 lines.push(Line::from(""));
168 return;
169 }
170
171 lines.push(Line::from("┌──────┬─────────────┐"));
173 lines.push(Line::styled(
174 "│ Byte │ Hex Bytes │",
175 Style::default().add_modifier(Modifier::BOLD),
176 ));
177 lines.push(Line::from("├──────┼─────────────┤"));
178
179 let entries_to_show = self.determine_sample_indices();
180
181 for (i, show_entry) in entries_to_show.iter().enumerate() {
182 if *show_entry {
183 let crc32 = self.pack_index.crc32_checksums[i];
184 let byte_pos = self.calculate_crc32_byte_position(i);
185 let hex_bytes = format!(
186 "{:02x} {:02x} {:02x} {:02x}",
187 (crc32 >> 24) & 0xff,
188 (crc32 >> 16) & 0xff,
189 (crc32 >> 8) & 0xff,
190 crc32 & 0xff
191 );
192
193 let line_parts = vec![
194 ("│ ".to_string(), Style::default()),
195 (format!("{:4}", byte_pos), Style::default().fg(Color::Cyan)),
196 (" │ ".to_string(), Style::default()),
197 (
198 format!("{:11}", hex_bytes),
199 Style::default().fg(Color::Blue),
200 ),
201 (" │".to_string(), Style::default()),
202 ];
203
204 let mut line = Line::default();
205 for (text, style) in line_parts {
206 line.spans.push(ratatui::text::Span::styled(text, style));
207 }
208 lines.push(line);
209 } else if i > 0 && entries_to_show[i - 1] {
210 let line_parts = vec![
212 ("│ ".to_string(), Style::default()),
213 ("...".to_string(), Style::default().fg(Color::Gray)),
214 (" │ ".to_string(), Style::default()),
215 ("...".to_string(), Style::default().fg(Color::Gray)),
216 (" │".to_string(), Style::default()),
217 ];
218
219 let mut line = Line::default();
220 for (text, style) in line_parts {
221 line.spans.push(ratatui::text::Span::styled(text, style));
222 }
223 lines.push(line);
224 }
225 }
226
227 lines.push(Line::from("└──────┴─────────────┘"));
228 lines.push(Line::from(""));
229 }
230
231 fn format_offsets_table(&self, lines: &mut Vec<Line<'static>>) {
232 lines.push(Line::styled(
233 "PACK FILE OFFSETS TABLE",
234 Style::default().add_modifier(Modifier::BOLD),
235 ));
236 lines.push(Line::from("─".repeat(30)));
237 lines.push(Line::from(""));
238
239 lines.push(Line::from("Pack file byte positions (4 bytes each)"));
240 lines.push(Line::from(format!(
241 "Total offsets: {}",
242 self.pack_index.offsets.len()
243 )));
244
245 let large_offset_count = self
247 .pack_index
248 .offsets
249 .iter()
250 .filter(|&&offset| offset & 0x80000000 != 0)
251 .count();
252
253 if large_offset_count > 0 {
254 lines.push(Line::styled(
255 format!(
256 "Large offsets: {} (MSB set, using 8-byte table)",
257 large_offset_count
258 ),
259 Style::default().fg(Color::Yellow),
260 ));
261 }
262 lines.push(Line::from(""));
263
264 if self.pack_index.offsets.is_empty() {
265 lines.push(Line::from("No offsets available."));
266 lines.push(Line::from(""));
267 return;
268 }
269
270 lines.push(Line::from("┌──────┬─────────────┬───────────┐"));
272 lines.push(Line::styled(
273 "│ Byte │ Hex Bytes │ Offset │",
274 Style::default().add_modifier(Modifier::BOLD),
275 ));
276 lines.push(Line::from("├──────┼─────────────┼───────────┤"));
277
278 let entries_to_show = self.determine_sample_indices();
279
280 for (i, show_entry) in entries_to_show.iter().enumerate() {
281 if *show_entry {
282 let raw_offset = self.pack_index.offsets[i];
283 let actual_offset = self.pack_index.get_object_offset(i);
284 let byte_pos = self.calculate_offset_byte_position(i);
285 let hex_bytes = format!(
286 "{:02x} {:02x} {:02x} {:02x}",
287 (raw_offset >> 24) & 0xff,
288 (raw_offset >> 16) & 0xff,
289 (raw_offset >> 8) & 0xff,
290 raw_offset & 0xff
291 );
292
293 let offset_display = if raw_offset & 0x80000000 != 0 {
294 format!("{:>8} L", actual_offset) } else {
296 format!("{:>9}", actual_offset)
297 };
298
299 let line_parts = vec![
300 ("│ ".to_string(), Style::default()),
301 (format!("{:4}", byte_pos), Style::default().fg(Color::Cyan)),
302 (" │ ".to_string(), Style::default()),
303 (
304 format!("{:10}", hex_bytes),
305 Style::default().fg(Color::Blue),
306 ),
307 (" │ ".to_string(), Style::default()),
308 (
309 offset_display,
310 if raw_offset & 0x80000000 != 0 {
311 Style::default().fg(Color::Yellow)
312 } else {
313 Style::default().fg(Color::Magenta)
314 },
315 ),
316 (" │".to_string(), Style::default()),
317 ];
318
319 let mut line = Line::default();
320 for (text, style) in line_parts {
321 line.spans.push(ratatui::text::Span::styled(text, style));
322 }
323 lines.push(line);
324 } else if i > 0 && entries_to_show[i - 1] {
325 let line_parts = vec![
327 ("│ ".to_string(), Style::default()),
328 ("...".to_string(), Style::default().fg(Color::Gray)),
329 (" │ ".to_string(), Style::default()),
330 ("...".to_string(), Style::default().fg(Color::Gray)),
331 (" │ ".to_string(), Style::default()),
332 ("...".to_string(), Style::default().fg(Color::Gray)),
333 (" │".to_string(), Style::default()),
334 ];
335
336 let mut line = Line::default();
337 for (text, style) in line_parts {
338 line.spans.push(ratatui::text::Span::styled(text, style));
339 }
340 lines.push(line);
341 }
342 }
343
344 lines.push(Line::from("└──────┴─────────────┴───────────┘"));
345 lines.push(Line::from(""));
346
347 if let Some(ref large_offsets) = self.pack_index.large_offsets {
349 self.add_large_offsets_info(lines, large_offsets);
350 }
351 }
352
353 fn add_large_offsets_info(&self, lines: &mut Vec<Line<'static>>, large_offsets: &[u64]) {
354 lines.push(Line::styled(
355 "Large Offset Table (8-byte offsets for pack files > 4GB):",
356 Style::default().add_modifier(Modifier::BOLD),
357 ));
358 lines.push(Line::from(format!("Entries: {}", large_offsets.len())));
359
360 if !large_offsets.is_empty() {
361 let min_large = large_offsets.iter().min().unwrap_or(&0);
362 let max_large = large_offsets.iter().max().unwrap_or(&0);
363 lines.push(Line::from(format!(
364 "Range: {} - {} bytes",
365 min_large, max_large
366 )));
367 }
368 lines.push(Line::from(""));
369 }
370
371 fn determine_sample_indices(&self) -> Vec<bool> {
374 let object_count = self.pack_index.object_count();
375 let mut show_entry = vec![false; object_count];
376
377 if object_count == 0 {
378 return show_entry;
379 }
380
381 if object_count <= 10 {
383 show_entry.fill(true);
384 return show_entry;
385 }
386
387 let sample_size = 3; for item in show_entry.iter_mut().take(sample_size.min(object_count)) {
392 *item = true;
393 }
394
395 let last_start = object_count.saturating_sub(sample_size);
397 for item in show_entry
398 .iter_mut()
399 .skip(last_start)
400 .take(object_count - last_start)
401 {
402 *item = true;
403 }
404
405 if object_count > 20 {
407 let mid_start = (object_count / 2).saturating_sub(1);
408 let mid_end = (mid_start + 2).min(object_count);
409
410 for item in show_entry
411 .iter_mut()
412 .skip(mid_start)
413 .take(mid_end - mid_start)
414 {
415 *item = true;
416 }
417 }
418
419 show_entry
420 }
421}