Skip to main content

oracle_lib/ui/
inspector.rs

1//! Inspector panel for displaying code item details
2
3use ratatui::{
4    buffer::Buffer,
5    layout::Rect,
6    style::{Modifier, Style},
7    text::{Line, Span},
8    widgets::{
9        block::BorderType, Block, Borders, Paragraph, Scrollbar, ScrollbarOrientation,
10        ScrollbarState, StatefulWidget, Widget, Wrap,
11    },
12};
13
14use crate::analyzer::{
15    AnalyzedItem, ConstInfo, EnumInfo, FunctionInfo, ImplInfo, ModuleInfo, StaticInfo, StructInfo,
16    StructKind, TraitInfo, TypeAliasInfo, VariantFields, Visibility,
17};
18use crate::ui::theme::Theme;
19
20/// Panel for inspecting code items with scrolling support
21pub struct InspectorPanel<'a> {
22    item: Option<&'a AnalyzedItem>,
23    /// All items (for "Implementations" of a trait)
24    all_items: Option<&'a [AnalyzedItem]>,
25    theme: &'a Theme,
26    focused: bool,
27    scroll_offset: usize,
28}
29
30impl<'a> InspectorPanel<'a> {
31    pub fn new(theme: &'a Theme) -> Self {
32        Self {
33            item: None,
34            all_items: None,
35            theme,
36            focused: false,
37            scroll_offset: 0,
38        }
39    }
40
41    pub fn item(mut self, item: Option<&'a AnalyzedItem>) -> Self {
42        self.item = item;
43        self
44    }
45
46    pub fn all_items(mut self, items: Option<&'a [AnalyzedItem]>) -> Self {
47        self.all_items = items;
48        self
49    }
50
51    pub fn focused(mut self, focused: bool) -> Self {
52        self.focused = focused;
53        self
54    }
55
56    pub fn scroll(mut self, offset: usize) -> Self {
57        self.scroll_offset = offset;
58        self
59    }
60
61    fn section_header(&self, title: &str) -> Line<'static> {
62        Line::from(vec![
63            Span::styled("▸ ", self.theme.style_accent()),
64            Span::styled(
65                title.to_string(),
66                self.theme.style_accent().add_modifier(Modifier::BOLD),
67            ),
68            Span::styled(" ─────────────────", self.theme.style_muted()),
69        ])
70    }
71
72    fn key_value(&self, key: &str, value: String) -> Line<'static> {
73        Line::from(vec![
74            Span::styled(format!("  {} ", key), self.theme.style_dim()),
75            Span::styled(value, self.theme.style_normal()),
76        ])
77    }
78
79    fn badge(&self, text: &str, is_warning: bool) -> Span<'static> {
80        let style = if is_warning {
81            self.theme.style_error().add_modifier(Modifier::BOLD)
82        } else {
83            self.theme.style_keyword()
84        };
85        Span::styled(format!(" [{}] ", text), style)
86    }
87
88    fn render_empty(&self, area: Rect, buf: &mut Buffer) {
89        let block = Block::default()
90            .borders(Borders::ALL)
91            .border_type(BorderType::Rounded)
92            .border_style(self.theme.style_border())
93            .style(Style::default().bg(self.theme.bg_panel))
94            .title(" ◇ Inspector ");
95
96        let inner = block.inner(area);
97        block.render(area, buf);
98
99        let help_text = vec![
100            Line::from(""),
101            Line::from(Span::styled("  No item selected", self.theme.style_muted())),
102            Line::from(""),
103            Line::from("  Select an item from the list to view"),
104            Line::from("  detailed API information."),
105            Line::from(""),
106            self.section_header("Navigation"),
107            Line::from(""),
108            Line::from(vec![
109                Span::raw("  "),
110                Span::styled("↑/↓ j/k", self.theme.style_accent()),
111                Span::raw("  Navigate list"),
112            ]),
113            Line::from(vec![
114                Span::raw("  "),
115                Span::styled("Enter/→", self.theme.style_accent()),
116                Span::raw("  View details"),
117            ]),
118            Line::from(vec![
119                Span::raw("  "),
120                Span::styled("/", self.theme.style_accent()),
121                Span::raw("        Search"),
122            ]),
123            Line::from(vec![
124                Span::raw("  "),
125                Span::styled("1-4", self.theme.style_accent()),
126                Span::raw("      Switch tabs"),
127            ]),
128        ];
129
130        Paragraph::new(help_text)
131            .wrap(Wrap { trim: false })
132            .render(inner, buf);
133    }
134
135    fn render_function(&self, func: &FunctionInfo, area: Rect, buf: &mut Buffer) {
136        let mut lines = Vec::new();
137
138        // Header with name and badges
139        let mut header = vec![
140            Span::styled("fn ", self.theme.style_keyword()),
141            Span::styled(
142                func.name.clone(),
143                self.theme
144                    .style_accent_bold()
145                    .add_modifier(Modifier::UNDERLINED),
146            ),
147        ];
148
149        if func.is_async {
150            header.push(self.badge("async", false));
151        }
152        if func.is_const {
153            header.push(self.badge("const", false));
154        }
155        if func.is_unsafe {
156            header.push(self.badge("unsafe", true));
157        }
158
159        lines.push(Line::from(header));
160
161        // Show qualified path if present
162        if !func.module_path.is_empty() {
163            lines.push(Line::from(vec![
164                Span::styled("  use ", self.theme.style_keyword()),
165                Span::styled(
166                    format!("{}::{}", func.module_path.join("::"), func.name),
167                    self.theme.style_type(),
168                ),
169                Span::styled(";", self.theme.style_normal()),
170            ]));
171        }
172        lines.push(Line::from(""));
173
174        // Full signature with syntax highlighting
175        lines.push(self.section_header("Signature"));
176        lines.push(Line::from(""));
177        for sig_line in func.signature.lines() {
178            lines.push(Line::from(vec![
179                Span::raw("  "),
180                Span::styled(sig_line.to_string(), self.theme.style_function()),
181            ]));
182        }
183
184        // Source Location
185        if func.source_location.file.is_some() {
186            lines.push(Line::from(""));
187            lines.push(self.section_header("Source"));
188            lines.push(Line::from(""));
189            lines.push(Line::from(vec![
190                Span::raw("  📍 "),
191                Span::styled(func.source_location.to_string(), self.theme.style_muted()),
192            ]));
193        }
194
195        // Overview
196        lines.push(Line::from(""));
197        lines.push(self.section_header("Overview"));
198        lines.push(Line::from(""));
199        lines.push(self.key_value("Visibility:", func.visibility.to_string()));
200
201        // Function properties
202        let mut props = Vec::new();
203        if func.is_async {
204            props.push("async");
205        }
206        if func.is_const {
207            props.push("const");
208        }
209        if func.is_unsafe {
210            props.push("unsafe");
211        }
212        if !props.is_empty() {
213            lines.push(self.key_value("Modifiers:", props.join(", ")));
214        }
215
216        if !func.generics.is_empty() {
217            lines.push(self.key_value("Generics:", format!("<{}>", func.generics.join(", "))));
218        }
219
220        // Parameters section with detailed analysis
221        if !func.parameters.is_empty() {
222            lines.push(Line::from(""));
223            lines.push(self.section_header(&format!("Parameters ({})", func.parameters.len())));
224            lines.push(Line::from(""));
225
226            for (i, param) in func.parameters.iter().enumerate() {
227                if param.is_self {
228                    let self_str = if param.is_ref && param.is_mut {
229                        "&mut self"
230                    } else if param.is_ref {
231                        "&self"
232                    } else if param.is_mut {
233                        "mut self"
234                    } else {
235                        "self"
236                    };
237
238                    let ownership_hint = if param.is_ref && param.is_mut {
239                        "mutable borrow"
240                    } else if param.is_ref {
241                        "immutable borrow"
242                    } else {
243                        "takes ownership"
244                    };
245
246                    lines.push(Line::from(vec![
247                        Span::styled(format!("  {}. ", i + 1), self.theme.style_number()),
248                        Span::styled(self_str, self.theme.style_keyword()),
249                    ]));
250                    lines.push(Line::from(vec![
251                        Span::raw("       "),
252                        Span::styled(format!("↳ {}", ownership_hint), self.theme.style_muted()),
253                    ]));
254                } else {
255                    lines.push(Line::from(vec![
256                        Span::styled(format!("  {}. ", i + 1), self.theme.style_number()),
257                        Span::styled(param.name.clone(), self.theme.style_accent()),
258                        Span::styled(": ", self.theme.style_muted()),
259                        Span::styled(param.ty.clone(), self.theme.style_type()),
260                    ]));
261
262                    // Ownership/borrowing analysis
263                    let ty_str = &param.ty;
264                    let hint = if ty_str.starts_with('&') && ty_str.contains("mut") {
265                        Some("↳ mutable borrow")
266                    } else if ty_str.starts_with('&') {
267                        Some("↳ immutable borrow")
268                    } else if ty_str.contains("impl ") || ty_str.contains("dyn ") {
269                        Some("↳ trait object/impl")
270                    } else if param.is_mut {
271                        Some("↳ mutable binding")
272                    } else {
273                        None
274                    };
275
276                    if let Some(h) = hint {
277                        lines.push(Line::from(vec![
278                            Span::raw("       "),
279                            Span::styled(h.to_string(), self.theme.style_muted()),
280                        ]));
281                    }
282                }
283            }
284        } else {
285            lines.push(Line::from(""));
286            lines.push(self.section_header("Parameters"));
287            lines.push(Line::from(""));
288            lines.push(Line::from(vec![
289                Span::raw("  "),
290                Span::styled("None (zero-argument function)", self.theme.style_muted()),
291            ]));
292        }
293
294        // Return type section
295        lines.push(Line::from(""));
296        lines.push(self.section_header("Returns"));
297        lines.push(Line::from(""));
298
299        if let Some(ref ret) = func.return_type {
300            lines.push(Line::from(vec![
301                Span::raw("  "),
302                Span::styled("→ ", self.theme.style_accent()),
303                Span::styled(ret.clone(), self.theme.style_type()),
304            ]));
305
306            // Add helpful hints for common return types
307            let ret_lower = ret.to_lowercase();
308            if ret_lower.contains("result") {
309                lines.push(Line::from(vec![
310                    Span::raw("       "),
311                    Span::styled(
312                        "⚠ Can fail - handle errors appropriately",
313                        self.theme.style_warning(),
314                    ),
315                ]));
316            } else if ret_lower.contains("option") {
317                lines.push(Line::from(vec![
318                    Span::raw("       "),
319                    Span::styled("⚠ May return None", self.theme.style_warning()),
320                ]));
321            }
322        } else {
323            lines.push(Line::from(vec![
324                Span::raw("  "),
325                Span::styled("→ ", self.theme.style_accent()),
326                Span::styled("() ", self.theme.style_type()),
327                Span::styled("(unit type)", self.theme.style_muted()),
328            ]));
329        }
330
331        // Where clause
332        if let Some(ref where_clause) = func.where_clause {
333            lines.push(Line::from(""));
334            lines.push(self.section_header("Constraints"));
335            lines.push(Line::from(""));
336            lines.push(Line::from(vec![
337                Span::raw("  "),
338                Span::styled("where ", self.theme.style_keyword()),
339                Span::styled(where_clause.clone(), self.theme.style_type()),
340            ]));
341        }
342
343        // Documentation
344        if let Some(ref docs) = func.documentation {
345            lines.push(Line::from(""));
346            lines.push(self.section_header("Documentation"));
347            lines.push(Line::from(""));
348            for doc_line in docs.lines() {
349                let trimmed = doc_line.trim_start_matches('/').trim_start();
350                lines.push(Line::from(Span::styled(
351                    format!("  {}", trimmed),
352                    self.theme.style_comment(),
353                )));
354            }
355        }
356
357        // Attributes
358        if !func.attributes.is_empty() {
359            lines.push(Line::from(""));
360            lines.push(self.section_header("Attributes"));
361            lines.push(Line::from(""));
362            for attr in &func.attributes {
363                lines.push(Line::from(Span::styled(
364                    format!("  #[{}]", attr),
365                    self.theme.style_muted(),
366                )));
367            }
368        }
369
370        self.render_panel(" 🔧 Function ", lines, area, buf);
371    }
372
373    fn render_struct(&self, st: &StructInfo, area: Rect, buf: &mut Buffer) {
374        let mut lines = Vec::new();
375
376        // Header with type badge
377        let kind_str = match st.kind {
378            StructKind::Named => "struct",
379            StructKind::Tuple => "tuple struct",
380            StructKind::Unit => "unit struct",
381        };
382
383        lines.push(Line::from(vec![
384            Span::styled("struct ", self.theme.style_keyword()),
385            Span::styled(
386                st.name.clone(),
387                self.theme
388                    .style_accent_bold()
389                    .add_modifier(Modifier::UNDERLINED),
390            ),
391            Span::raw(" "),
392            Span::styled(format!("({})", kind_str), self.theme.style_muted()),
393        ]));
394
395        // Show qualified path if present
396        if !st.module_path.is_empty() {
397            lines.push(Line::from(vec![
398                Span::styled("  use ", self.theme.style_keyword()),
399                Span::styled(
400                    format!("{}::{}", st.module_path.join("::"), st.name),
401                    self.theme.style_type(),
402                ),
403                Span::styled(";", self.theme.style_normal()),
404            ]));
405        }
406        lines.push(Line::from(""));
407
408        // Full Definition
409        lines.push(self.section_header("Definition"));
410        lines.push(Line::from(""));
411        for line in st.full_definition().lines() {
412            lines.push(Line::from(vec![
413                Span::raw("  "),
414                Span::styled(line.to_string(), self.theme.style_function()),
415            ]));
416        }
417
418        // Source Location
419        if st.source_location.file.is_some() {
420            lines.push(Line::from(""));
421            lines.push(self.section_header("Source"));
422            lines.push(Line::from(""));
423            lines.push(Line::from(vec![
424                Span::raw("  📍 "),
425                Span::styled(st.source_location.to_string(), self.theme.style_muted()),
426            ]));
427        }
428
429        // Overview
430        lines.push(Line::from(""));
431        lines.push(self.section_header("Overview"));
432        lines.push(Line::from(""));
433        lines.push(self.key_value("Visibility:", st.visibility.to_string()));
434        lines.push(self.key_value("Kind:", kind_str.to_string()));
435        lines.push(self.key_value("Field Count:", st.fields.len().to_string()));
436
437        if !st.generics.is_empty() {
438            lines.push(self.key_value("Generics:", format!("<{}>", st.generics.join(", "))));
439        }
440
441        if let Some(ref wc) = st.where_clause {
442            lines.push(self.key_value("Where:", wc.clone()));
443        }
444
445        // Derives with categorization
446        if !st.derives.is_empty() {
447            lines.push(Line::from(""));
448            lines.push(self.section_header("Derived Traits"));
449            lines.push(Line::from(""));
450
451            // Categorize derives
452            let (standard, serde_derives, other): (Vec<_>, Vec<_>, Vec<_>) = st
453                .derives
454                .iter()
455                .fold((vec![], vec![], vec![]), |mut acc, d| {
456                    let d_lower = d.to_lowercase();
457                    if [
458                        "debug",
459                        "clone",
460                        "copy",
461                        "default",
462                        "partialeq",
463                        "eq",
464                        "partialord",
465                        "ord",
466                        "hash",
467                    ]
468                    .contains(&d_lower.as_str())
469                    {
470                        acc.0.push(d);
471                    } else if d_lower.contains("serde")
472                        || d_lower == "serialize"
473                        || d_lower == "deserialize"
474                    {
475                        acc.1.push(d);
476                    } else {
477                        acc.2.push(d);
478                    }
479                    acc
480                });
481
482            if !standard.is_empty() {
483                lines.push(Line::from(vec![
484                    Span::raw("  "),
485                    Span::styled("Standard: ", self.theme.style_dim()),
486                    Span::styled(
487                        standard
488                            .iter()
489                            .map(|s| s.as_str())
490                            .collect::<Vec<_>>()
491                            .join(", "),
492                        self.theme.style_type(),
493                    ),
494                ]));
495            }
496            if !serde_derives.is_empty() {
497                lines.push(Line::from(vec![
498                    Span::raw("  "),
499                    Span::styled("Serde: ", self.theme.style_dim()),
500                    Span::styled(
501                        serde_derives
502                            .iter()
503                            .map(|s| s.as_str())
504                            .collect::<Vec<_>>()
505                            .join(", "),
506                        self.theme.style_type(),
507                    ),
508                ]));
509            }
510            if !other.is_empty() {
511                lines.push(Line::from(vec![
512                    Span::raw("  "),
513                    Span::styled("Other: ", self.theme.style_dim()),
514                    Span::styled(
515                        other
516                            .iter()
517                            .map(|s| s.as_str())
518                            .collect::<Vec<_>>()
519                            .join(", "),
520                        self.theme.style_type(),
521                    ),
522                ]));
523            }
524        }
525
526        // Fields with detailed info
527        if !st.fields.is_empty() {
528            lines.push(Line::from(""));
529            lines.push(self.section_header(&format!("Fields ({})", st.fields.len())));
530            lines.push(Line::from(""));
531
532            for (i, field) in st.fields.iter().enumerate() {
533                let vis_str = match field.visibility {
534                    Visibility::Public => "pub ",
535                    Visibility::Crate => "pub(crate) ",
536                    Visibility::Super => "pub(super) ",
537                    _ => "",
538                };
539
540                lines.push(Line::from(vec![
541                    Span::styled(format!("  {}. ", i + 1), self.theme.style_number()),
542                    Span::styled(vis_str.to_string(), self.theme.style_keyword()),
543                    Span::styled(field.name.clone(), self.theme.style_accent()),
544                    Span::styled(": ", self.theme.style_muted()),
545                    Span::styled(field.ty.clone(), self.theme.style_type()),
546                ]));
547
548                // Type analysis hints
549                let ty_lower = field.ty.to_lowercase();
550                if ty_lower.contains("option") {
551                    lines.push(Line::from(vec![
552                        Span::raw("       "),
553                        Span::styled("⚪ Optional field", self.theme.style_muted()),
554                    ]));
555                } else if ty_lower.contains("vec")
556                    || ty_lower.contains("hashmap")
557                    || ty_lower.contains("btreemap")
558                {
559                    lines.push(Line::from(vec![
560                        Span::raw("       "),
561                        Span::styled("📦 Collection type", self.theme.style_muted()),
562                    ]));
563                } else if ty_lower.contains("box")
564                    || ty_lower.contains("rc")
565                    || ty_lower.contains("arc")
566                {
567                    lines.push(Line::from(vec![
568                        Span::raw("       "),
569                        Span::styled("🔗 Heap-allocated/Shared", self.theme.style_muted()),
570                    ]));
571                }
572
573                if let Some(ref doc) = field.documentation {
574                    for doc_line in doc.lines().take(2) {
575                        let trimmed = doc_line.trim_start_matches('/').trim_start();
576                        if !trimmed.is_empty() {
577                            lines.push(Line::from(vec![
578                                Span::raw("       "),
579                                Span::styled(trimmed.to_string(), self.theme.style_comment()),
580                            ]));
581                        }
582                    }
583                }
584            }
585        }
586
587        // Type usage hints
588        lines.push(Line::from(""));
589        lines.push(self.section_header("Usage"));
590        lines.push(Line::from(""));
591
592        // Construction hint
593        match st.kind {
594            StructKind::Named => {
595                lines.push(Line::from(vec![
596                    Span::raw("  "),
597                    Span::styled("let instance = ", self.theme.style_dim()),
598                    Span::styled(
599                        format!("{} {{ ... }};", st.name),
600                        self.theme.style_function(),
601                    ),
602                ]));
603            }
604            StructKind::Tuple => {
605                let placeholders = (0..st.fields.len())
606                    .map(|_| "_")
607                    .collect::<Vec<_>>()
608                    .join(", ");
609                lines.push(Line::from(vec![
610                    Span::raw("  "),
611                    Span::styled("let instance = ", self.theme.style_dim()),
612                    Span::styled(
613                        format!("{}({});", st.name, placeholders),
614                        self.theme.style_function(),
615                    ),
616                ]));
617            }
618            StructKind::Unit => {
619                lines.push(Line::from(vec![
620                    Span::raw("  "),
621                    Span::styled("let instance = ", self.theme.style_dim()),
622                    Span::styled(format!("{};", st.name), self.theme.style_function()),
623                ]));
624            }
625        }
626
627        // Show Default hint if derived
628        if st.derives.iter().any(|d| d.to_lowercase() == "default") {
629            lines.push(Line::from(vec![
630                Span::raw("  "),
631                Span::styled("let instance = ", self.theme.style_dim()),
632                Span::styled(
633                    format!("{}::default();", st.name),
634                    self.theme.style_function(),
635                ),
636            ]));
637        }
638
639        // Documentation
640        if let Some(ref docs) = st.documentation {
641            lines.push(Line::from(""));
642            lines.push(self.section_header("Documentation"));
643            lines.push(Line::from(""));
644            for doc_line in docs.lines() {
645                let trimmed = doc_line.trim_start_matches('/').trim_start();
646                lines.push(Line::from(Span::styled(
647                    format!("  {}", trimmed),
648                    self.theme.style_comment(),
649                )));
650            }
651        }
652
653        // Attributes
654        if !st.attributes.is_empty() {
655            lines.push(Line::from(""));
656            lines.push(self.section_header("Attributes"));
657            lines.push(Line::from(""));
658            for attr in &st.attributes {
659                lines.push(Line::from(vec![
660                    Span::raw("  "),
661                    Span::styled(format!("#[{}]", attr), self.theme.style_muted()),
662                ]));
663            }
664        }
665
666        self.render_panel(" 📦 Struct ", lines, area, buf);
667    }
668
669    fn render_enum(&self, en: &EnumInfo, area: Rect, buf: &mut Buffer) {
670        let mut lines = Vec::new();
671
672        lines.push(Line::from(vec![
673            Span::styled("enum ", self.theme.style_keyword()),
674            Span::styled(
675                en.name.clone(),
676                self.theme
677                    .style_accent_bold()
678                    .add_modifier(Modifier::UNDERLINED),
679            ),
680        ]));
681
682        // Show qualified path if present
683        if !en.module_path.is_empty() {
684            lines.push(Line::from(vec![
685                Span::styled("  use ", self.theme.style_keyword()),
686                Span::styled(
687                    format!("{}::{}", en.module_path.join("::"), en.name),
688                    self.theme.style_type(),
689                ),
690                Span::styled(";", self.theme.style_normal()),
691            ]));
692        }
693        lines.push(Line::from(""));
694
695        // Overview
696        lines.push(self.section_header("Overview"));
697        lines.push(Line::from(""));
698        lines.push(self.key_value("Visibility:", en.visibility.to_string()));
699        lines.push(self.key_value("Variants:", en.variants.len().to_string()));
700
701        if !en.generics.is_empty() {
702            lines.push(self.key_value("Generics:", format!("<{}>", en.generics.join(", "))));
703        }
704
705        // Derives
706        if !en.derives.is_empty() {
707            lines.push(Line::from(""));
708            lines.push(self.section_header("Derived Traits"));
709            lines.push(Line::from(""));
710            for derive in &en.derives {
711                lines.push(Line::from(vec![
712                    Span::raw("  • "),
713                    Span::styled(derive.clone(), self.theme.style_type()),
714                ]));
715            }
716        }
717
718        // Variants
719        lines.push(Line::from(""));
720        lines.push(self.section_header(&format!("Variants ({})", en.variants.len())));
721        lines.push(Line::from(""));
722
723        for (i, variant) in en.variants.iter().enumerate() {
724            let fields_str = match &variant.fields {
725                VariantFields::Named(fields) => {
726                    let f: Vec<_> = fields
727                        .iter()
728                        .map(|f| format!("{}: {}", f.name, f.ty))
729                        .collect();
730                    format!(" {{ {} }}", f.join(", "))
731                }
732                VariantFields::Unnamed(types) => format!("({})", types.join(", ")),
733                VariantFields::Unit => String::new(),
734            };
735
736            let discriminant = variant
737                .discriminant
738                .as_ref()
739                .map(|d| format!(" = {}", d))
740                .unwrap_or_default();
741
742            lines.push(Line::from(vec![
743                Span::styled(format!("  {}. ", i + 1), self.theme.style_dim()),
744                Span::styled(variant.name.clone(), self.theme.style_type()),
745                Span::styled(fields_str, self.theme.style_muted()),
746                Span::styled(discriminant, self.theme.style_number()),
747            ]));
748
749            if let Some(ref doc) = variant.documentation {
750                let first_line = doc.lines().next().unwrap_or("");
751                lines.push(Line::from(vec![
752                    Span::raw("       "),
753                    Span::styled(first_line.to_string(), self.theme.style_comment()),
754                ]));
755            }
756        }
757
758        // Documentation
759        if let Some(ref docs) = en.documentation {
760            lines.push(Line::from(""));
761            lines.push(self.section_header("Documentation"));
762            lines.push(Line::from(""));
763            for doc_line in docs.lines() {
764                let trimmed = doc_line.trim_start_matches('/').trim_start();
765                lines.push(Line::from(Span::styled(
766                    format!("  {}", trimmed),
767                    self.theme.style_comment(),
768                )));
769            }
770        }
771
772        self.render_panel(" 🏷️ Enum ", lines, area, buf);
773    }
774
775    fn render_trait(&self, tr: &TraitInfo, area: Rect, buf: &mut Buffer) {
776        let mut lines = Vec::new();
777
778        let mut header = vec![
779            Span::styled("trait ", self.theme.style_keyword()),
780            Span::styled(
781                tr.name.clone(),
782                self.theme
783                    .style_accent_bold()
784                    .add_modifier(Modifier::UNDERLINED),
785            ),
786        ];
787
788        if tr.is_unsafe {
789            header.push(self.badge("unsafe", true));
790        }
791        if tr.is_auto {
792            header.push(self.badge("auto", false));
793        }
794
795        lines.push(Line::from(header));
796
797        // Show qualified path if present
798        if !tr.module_path.is_empty() {
799            lines.push(Line::from(vec![
800                Span::styled("  use ", self.theme.style_keyword()),
801                Span::styled(
802                    format!("{}::{}", tr.module_path.join("::"), tr.name),
803                    self.theme.style_type(),
804                ),
805                Span::styled(";", self.theme.style_normal()),
806            ]));
807        }
808        lines.push(Line::from(""));
809
810        // Overview
811        lines.push(self.section_header("Overview"));
812        lines.push(Line::from(""));
813        lines.push(self.key_value("Visibility:", tr.visibility.to_string()));
814        lines.push(self.key_value("Methods:", tr.methods.len().to_string()));
815
816        if !tr.associated_types.is_empty() {
817            lines.push(self.key_value("Associated Types:", tr.associated_types.len().to_string()));
818        }
819
820        // Supertraits
821        if !tr.supertraits.is_empty() {
822            lines.push(Line::from(""));
823            lines.push(self.section_header("Supertraits"));
824            lines.push(Line::from(""));
825            lines.push(Line::from(vec![
826                Span::raw("  "),
827                Span::styled(tr.supertraits.join(" + "), self.theme.style_type()),
828            ]));
829        }
830
831        // Associated types
832        if !tr.associated_types.is_empty() {
833            lines.push(Line::from(""));
834            lines.push(self.section_header("Associated Types"));
835            lines.push(Line::from(""));
836            for at in &tr.associated_types {
837                let bounds = if at.bounds.is_empty() {
838                    String::new()
839                } else {
840                    format!(": {}", at.bounds.join(" + "))
841                };
842                let default = at
843                    .default
844                    .as_ref()
845                    .map(|d| format!(" = {}", d))
846                    .unwrap_or_default();
847
848                lines.push(Line::from(vec![
849                    Span::raw("  "),
850                    Span::styled("type ", self.theme.style_keyword()),
851                    Span::styled(at.name.clone(), self.theme.style_type()),
852                    Span::styled(bounds, self.theme.style_muted()),
853                    Span::styled(default, self.theme.style_type()),
854                ]));
855            }
856        }
857
858        // Implementations (impl Trait for Type)
859        if let Some(all) = self.all_items {
860            let impls: Vec<&ImplInfo> = all
861                .iter()
862                .filter_map(|i| {
863                    if let AnalyzedItem::Impl(im) = i {
864                        let matches = im.trait_name.as_deref().is_some_and(|tn| {
865                            tn == tr.name || tn.ends_with(&format!("::{}", tr.name))
866                        });
867                        if matches {
868                            Some(im)
869                        } else {
870                            None
871                        }
872                    } else {
873                        None
874                    }
875                })
876                .collect();
877            if !impls.is_empty() {
878                lines.push(Line::from(""));
879                lines.push(self.section_header(&format!("Implementations ({})", impls.len())));
880                lines.push(Line::from(""));
881                for (i, im) in impls.iter().enumerate() {
882                    lines.push(Line::from(vec![
883                        Span::styled(format!("  {}. ", i + 1), self.theme.style_dim()),
884                        Span::styled(im.full_definition(), self.theme.style_type()),
885                    ]));
886                }
887            }
888        }
889
890        // Methods
891        if !tr.methods.is_empty() {
892            lines.push(Line::from(""));
893            lines.push(self.section_header(&format!("Methods ({})", tr.methods.len())));
894            lines.push(Line::from(""));
895
896            for (i, method) in tr.methods.iter().enumerate() {
897                let mut method_line = vec![
898                    Span::styled(format!("  {}. ", i + 1), self.theme.style_dim()),
899                    Span::styled("fn ", self.theme.style_keyword()),
900                    Span::styled(method.name.clone(), self.theme.style_function()),
901                ];
902
903                if method.has_default {
904                    method_line.push(Span::styled(" [default]", self.theme.style_success()));
905                }
906                if method.is_async {
907                    method_line.push(Span::styled(" async", self.theme.style_keyword()));
908                }
909
910                lines.push(Line::from(method_line));
911
912                if let Some(ref doc) = method.documentation {
913                    let first_line = doc.lines().next().unwrap_or("");
914                    lines.push(Line::from(vec![
915                        Span::raw("       "),
916                        Span::styled(first_line.to_string(), self.theme.style_comment()),
917                    ]));
918                }
919            }
920        }
921
922        // Documentation
923        if let Some(ref docs) = tr.documentation {
924            lines.push(Line::from(""));
925            lines.push(self.section_header("Documentation"));
926            lines.push(Line::from(""));
927            for doc_line in docs.lines() {
928                let trimmed = doc_line.trim_start_matches('/').trim_start();
929                lines.push(Line::from(Span::styled(
930                    format!("  {}", trimmed),
931                    self.theme.style_comment(),
932                )));
933            }
934        }
935
936        self.render_panel(" 📜 Trait ", lines, area, buf);
937    }
938
939    fn render_impl(&self, im: &ImplInfo, area: Rect, buf: &mut Buffer) {
940        let mut lines = Vec::new();
941
942        let title = if let Some(ref trait_name) = im.trait_name {
943            format!("impl {} for {}", trait_name, im.self_ty)
944        } else {
945            format!("impl {}", im.self_ty)
946        };
947
948        let mut header = vec![
949            Span::styled("impl ", self.theme.style_keyword()),
950            Span::styled(title, self.theme.style_accent_bold()),
951        ];
952
953        if im.is_unsafe {
954            header.push(self.badge("unsafe", true));
955        }
956        if im.is_negative {
957            header.push(self.badge("negative", true));
958        }
959
960        lines.push(Line::from(header));
961        lines.push(Line::from(""));
962
963        // Overview
964        lines.push(self.section_header("Overview"));
965        lines.push(Line::from(""));
966        lines.push(self.key_value("Type:", im.self_ty.clone()));
967
968        if let Some(ref trait_name) = im.trait_name {
969            lines.push(self.key_value("Trait:", trait_name.clone()));
970        }
971
972        lines.push(self.key_value("Methods:", im.methods.len().to_string()));
973
974        if !im.generics.is_empty() {
975            lines.push(self.key_value("Generics:", format!("<{}>", im.generics.join(", "))));
976        }
977
978        // Methods
979        if !im.methods.is_empty() {
980            lines.push(Line::from(""));
981            lines.push(self.section_header(&format!("Methods ({})", im.methods.len())));
982            lines.push(Line::from(""));
983
984            for (i, method) in im.methods.iter().enumerate() {
985                let vis = if method.visibility == Visibility::Public {
986                    "pub "
987                } else {
988                    ""
989                };
990
991                let mut method_line = vec![
992                    Span::styled(format!("  {}. ", i + 1), self.theme.style_dim()),
993                    Span::styled(vis.to_string(), self.theme.style_keyword()),
994                    Span::styled("fn ", self.theme.style_keyword()),
995                    Span::styled(method.name.clone(), self.theme.style_function()),
996                ];
997
998                // Show parameter count
999                let param_count = method.parameters.len();
1000                method_line.push(Span::styled(
1001                    format!("({} params)", param_count),
1002                    self.theme.style_muted(),
1003                ));
1004
1005                // Show return type hint
1006                if let Some(ref ret) = method.return_type {
1007                    method_line.push(Span::styled(" → ", self.theme.style_accent()));
1008                    method_line.push(Span::styled(ret.clone(), self.theme.style_type()));
1009                }
1010
1011                lines.push(Line::from(method_line));
1012            }
1013        }
1014
1015        // Where clause
1016        if let Some(ref where_clause) = im.where_clause {
1017            lines.push(Line::from(""));
1018            lines.push(self.section_header("Constraints"));
1019            lines.push(Line::from(""));
1020            lines.push(Line::from(vec![
1021                Span::raw("  "),
1022                Span::styled("where ", self.theme.style_keyword()),
1023                Span::styled(where_clause.clone(), self.theme.style_type()),
1024            ]));
1025        }
1026
1027        self.render_panel(" ⚙️ Implementation ", lines, area, buf);
1028    }
1029
1030    fn render_module(&self, module: &ModuleInfo, area: Rect, buf: &mut Buffer) {
1031        let mut lines = vec![
1032            Line::from(vec![
1033                Span::styled("mod ", self.theme.style_keyword()),
1034                Span::styled(
1035                    module.name.clone(),
1036                    self.theme
1037                        .style_accent_bold()
1038                        .add_modifier(Modifier::UNDERLINED),
1039                ),
1040            ]),
1041            Line::from(""),
1042            self.section_header("Overview"),
1043            Line::from(""),
1044            self.key_value("Visibility:", module.visibility.to_string()),
1045            self.key_value("Path:", module.path.clone()),
1046            self.key_value(
1047                "Inline:",
1048                if module.is_inline { "yes" } else { "no" }.to_string(),
1049            ),
1050        ];
1051
1052        // Submodules (flow / tree)
1053        if !module.submodules.is_empty() {
1054            lines.push(Line::from(""));
1055            lines.push(
1056                self.section_header(&format!("Submodules / flow ({})", module.submodules.len())),
1057            );
1058            lines.push(Line::from(""));
1059            let n = module.submodules.len();
1060            for (i, submod) in module.submodules.iter().enumerate() {
1061                let connector = if i == n - 1 {
1062                    "└── "
1063                } else {
1064                    "├── "
1065                };
1066                lines.push(Line::from(vec![
1067                    Span::raw("  "),
1068                    Span::styled(connector, self.theme.style_muted()),
1069                    Span::styled(submod.clone(), self.theme.style_accent()),
1070                ]));
1071            }
1072        }
1073
1074        // Items
1075        if !module.items.is_empty() {
1076            lines.push(Line::from(""));
1077            lines.push(self.section_header(&format!("Items ({})", module.items.len())));
1078            lines.push(Line::from(""));
1079            for item in module.items.iter().take(20) {
1080                lines.push(Line::from(vec![
1081                    Span::raw("  • "),
1082                    Span::styled(item.clone(), self.theme.style_normal()),
1083                ]));
1084            }
1085            if module.items.len() > 20 {
1086                lines.push(Line::from(vec![
1087                    Span::raw("  "),
1088                    Span::styled(
1089                        format!("... and {} more", module.items.len() - 20),
1090                        self.theme.style_muted(),
1091                    ),
1092                ]));
1093            }
1094        }
1095
1096        // Documentation
1097        if let Some(ref docs) = module.documentation {
1098            lines.push(Line::from(""));
1099            lines.push(self.section_header("Documentation"));
1100            lines.push(Line::from(""));
1101            for doc_line in docs.lines() {
1102                let trimmed = doc_line.trim_start_matches('/').trim_start();
1103                lines.push(Line::from(Span::styled(
1104                    format!("  {}", trimmed),
1105                    self.theme.style_comment(),
1106                )));
1107            }
1108        }
1109
1110        self.render_panel(" 📁 Module ", lines, area, buf);
1111    }
1112
1113    fn render_type_alias(&self, alias: &TypeAliasInfo, area: Rect, buf: &mut Buffer) {
1114        let mut lines = vec![
1115            Line::from(vec![
1116                Span::styled("type ", self.theme.style_keyword()),
1117                Span::styled(
1118                    alias.name.clone(),
1119                    self.theme
1120                        .style_accent_bold()
1121                        .add_modifier(Modifier::UNDERLINED),
1122                ),
1123                Span::styled(" = ", self.theme.style_muted()),
1124                Span::styled(alias.ty.clone(), self.theme.style_type()),
1125            ]),
1126            Line::from(""),
1127            self.section_header("Overview"),
1128            Line::from(""),
1129            self.key_value("Visibility:", alias.visibility.to_string()),
1130            self.key_value("Aliased Type:", alias.ty.clone()),
1131        ];
1132
1133        if !alias.generics.is_empty() {
1134            lines.push(self.key_value("Generics:", format!("<{}>", alias.generics.join(", "))));
1135        }
1136
1137        if let Some(ref docs) = alias.documentation {
1138            lines.push(Line::from(""));
1139            lines.push(self.section_header("Documentation"));
1140            lines.push(Line::from(""));
1141            for doc_line in docs.lines() {
1142                let trimmed = doc_line.trim_start_matches('/').trim_start();
1143                lines.push(Line::from(Span::styled(
1144                    format!("  {}", trimmed),
1145                    self.theme.style_comment(),
1146                )));
1147            }
1148        }
1149
1150        self.render_panel(" 🔗 Type Alias ", lines, area, buf);
1151    }
1152
1153    fn render_const(&self, c: &ConstInfo, area: Rect, buf: &mut Buffer) {
1154        let mut lines = vec![
1155            Line::from(vec![
1156                Span::styled("const ", self.theme.style_keyword()),
1157                Span::styled(
1158                    c.name.clone(),
1159                    self.theme
1160                        .style_accent_bold()
1161                        .add_modifier(Modifier::UNDERLINED),
1162                ),
1163                Span::styled(": ", self.theme.style_muted()),
1164                Span::styled(c.ty.clone(), self.theme.style_type()),
1165            ]),
1166            Line::from(""),
1167            self.section_header("Overview"),
1168            Line::from(""),
1169            self.key_value("Visibility:", c.visibility.to_string()),
1170            self.key_value("Type:", c.ty.clone()),
1171        ];
1172
1173        if let Some(ref value) = c.value {
1174            lines.push(self.key_value("Value:", value.clone()));
1175        }
1176
1177        if let Some(ref docs) = c.documentation {
1178            lines.push(Line::from(""));
1179            lines.push(self.section_header("Documentation"));
1180            lines.push(Line::from(""));
1181            for doc_line in docs.lines() {
1182                let trimmed = doc_line.trim_start_matches('/').trim_start();
1183                lines.push(Line::from(Span::styled(
1184                    format!("  {}", trimmed),
1185                    self.theme.style_comment(),
1186                )));
1187            }
1188        }
1189
1190        self.render_panel(" 📌 Constant ", lines, area, buf);
1191    }
1192
1193    fn render_static(&self, s: &StaticInfo, area: Rect, buf: &mut Buffer) {
1194        let mut lines = Vec::new();
1195
1196        let mut header = vec![Span::styled("static ", self.theme.style_keyword())];
1197
1198        if s.is_mut {
1199            header.push(Span::styled("mut ", self.theme.style_keyword()));
1200        }
1201
1202        header.push(Span::styled(
1203            s.name.clone(),
1204            self.theme
1205                .style_accent_bold()
1206                .add_modifier(Modifier::UNDERLINED),
1207        ));
1208        header.push(Span::styled(": ", self.theme.style_muted()));
1209        header.push(Span::styled(s.ty.clone(), self.theme.style_type()));
1210
1211        if s.is_mut {
1212            header.push(self.badge("mutable", true));
1213        }
1214
1215        lines.push(Line::from(header));
1216        lines.push(Line::from(""));
1217
1218        lines.push(self.section_header("Overview"));
1219        lines.push(Line::from(""));
1220        lines.push(self.key_value("Visibility:", s.visibility.to_string()));
1221        lines.push(self.key_value("Type:", s.ty.clone()));
1222        lines.push(self.key_value(
1223            "Mutable:",
1224            if s.is_mut { "yes ⚠️" } else { "no" }.to_string(),
1225        ));
1226
1227        if s.is_mut {
1228            lines.push(Line::from(""));
1229            lines.push(Line::from(vec![
1230                Span::raw("  "),
1231                Span::styled("⚠ Warning: ", self.theme.style_error()),
1232                Span::styled("Mutable statics are unsafe!", self.theme.style_warning()),
1233            ]));
1234        }
1235
1236        if let Some(ref docs) = s.documentation {
1237            lines.push(Line::from(""));
1238            lines.push(self.section_header("Documentation"));
1239            lines.push(Line::from(""));
1240            for doc_line in docs.lines() {
1241                let trimmed = doc_line.trim_start_matches('/').trim_start();
1242                lines.push(Line::from(Span::styled(
1243                    format!("  {}", trimmed),
1244                    self.theme.style_comment(),
1245                )));
1246            }
1247        }
1248
1249        self.render_panel(" 🌐 Static ", lines, area, buf);
1250    }
1251
1252    fn render_panel(&self, title: &str, lines: Vec<Line<'static>>, area: Rect, buf: &mut Buffer) {
1253        let total_lines = lines.len();
1254
1255        let block = Block::default()
1256            .borders(Borders::ALL)
1257            .border_type(BorderType::Rounded)
1258            .border_style(if self.focused {
1259                self.theme.style_border_focused()
1260            } else {
1261                self.theme.style_border()
1262            })
1263            .style(Style::default().bg(self.theme.bg_panel))
1264            .title(title);
1265
1266        let inner = block.inner(area);
1267        block.render(area, buf);
1268
1269        // Apply scroll offset
1270        let visible_lines: Vec<Line> = lines.into_iter().skip(self.scroll_offset).collect();
1271
1272        Paragraph::new(visible_lines)
1273            .wrap(Wrap { trim: false })
1274            .render(inner, buf);
1275
1276        // Render scrollbar if content exceeds view
1277        if total_lines > inner.height as usize {
1278            let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
1279                .begin_symbol(Some("↑"))
1280                .end_symbol(Some("↓"));
1281
1282            let mut scrollbar_state = ScrollbarState::new(total_lines).position(self.scroll_offset);
1283
1284            scrollbar.render(inner, buf, &mut scrollbar_state);
1285        }
1286    }
1287}
1288
1289impl Widget for InspectorPanel<'_> {
1290    fn render(self, area: Rect, buf: &mut Buffer) {
1291        match self.item {
1292            None => self.render_empty(area, buf),
1293            Some(AnalyzedItem::Function(f)) => self.render_function(f, area, buf),
1294            Some(AnalyzedItem::Struct(s)) => self.render_struct(s, area, buf),
1295            Some(AnalyzedItem::Enum(e)) => self.render_enum(e, area, buf),
1296            Some(AnalyzedItem::Trait(t)) => self.render_trait(t, area, buf),
1297            Some(AnalyzedItem::Impl(i)) => self.render_impl(i, area, buf),
1298            Some(AnalyzedItem::Module(m)) => self.render_module(m, area, buf),
1299            Some(AnalyzedItem::TypeAlias(t)) => self.render_type_alias(t, area, buf),
1300            Some(AnalyzedItem::Const(c)) => self.render_const(c, area, buf),
1301            Some(AnalyzedItem::Static(s)) => self.render_static(s, area, buf),
1302        }
1303    }
1304}