mockforge_tui/widgets/
json_viewer.rs1use ratatui::{
4 layout::Rect,
5 text::{Line, Span},
6 widgets::{Block, Borders, Paragraph},
7 Frame,
8};
9
10use crate::theme::Theme;
11
12pub fn render(frame: &mut Frame, area: Rect, title: &str, value: &serde_json::Value) {
14 render_scrollable(frame, area, title, value, 0);
15}
16
17pub fn render_scrollable(
19 frame: &mut Frame,
20 area: Rect,
21 title: &str,
22 value: &serde_json::Value,
23 scroll_offset: u16,
24) {
25 let lines = json_to_lines(value, 0);
26
27 let block = Block::default()
28 .title(format!(" {title} "))
29 .title_style(Theme::title())
30 .borders(Borders::ALL)
31 .border_style(Theme::dim())
32 .style(Theme::surface());
33
34 let paragraph = Paragraph::new(lines).block(block).scroll((scroll_offset, 0));
35 frame.render_widget(paragraph, area);
36}
37
38fn json_to_lines(value: &serde_json::Value, depth: usize) -> Vec<Line<'static>> {
39 let indent = " ".repeat(depth);
40 match value {
41 serde_json::Value::Object(map) => {
42 let mut lines = Vec::new();
43 for (key, val) in map {
44 match val {
45 serde_json::Value::Object(_) | serde_json::Value::Array(_) => {
46 lines.push(Line::from(vec![
47 Span::raw(indent.clone()),
48 Span::styled(format!("{key}: "), Theme::key_hint()),
49 ]));
50 lines.extend(json_to_lines(val, depth + 1));
51 }
52 _ => {
53 lines.push(Line::from(vec![
54 Span::raw(indent.clone()),
55 Span::styled(format!("{key}: "), Theme::key_hint()),
56 value_span(val),
57 ]));
58 }
59 }
60 }
61 lines
62 }
63 serde_json::Value::Array(arr) => {
64 let mut lines = Vec::new();
65 for (i, val) in arr.iter().enumerate() {
66 match val {
67 serde_json::Value::Object(_) | serde_json::Value::Array(_) => {
68 lines.push(Line::from(vec![
69 Span::raw(indent.clone()),
70 Span::styled(format!("[{i}]:"), Theme::dim()),
71 ]));
72 lines.extend(json_to_lines(val, depth + 1));
73 }
74 _ => {
75 lines.push(Line::from(vec![
76 Span::raw(indent.clone()),
77 Span::styled(format!("[{i}]: "), Theme::dim()),
78 value_span(val),
79 ]));
80 }
81 }
82 }
83 lines
84 }
85 _ => {
86 vec![Line::from(vec![Span::raw(indent), value_span(value)])]
87 }
88 }
89}
90
91fn value_span(value: &serde_json::Value) -> Span<'static> {
92 match value {
93 serde_json::Value::String(s) => {
94 Span::styled(format!("\"{s}\""), ratatui::style::Style::default().fg(Theme::GREEN))
95 }
96 serde_json::Value::Number(n) => {
97 Span::styled(n.to_string(), ratatui::style::Style::default().fg(Theme::PEACH))
98 }
99 serde_json::Value::Bool(b) => {
100 let color = if *b { Theme::GREEN } else { Theme::RED };
101 Span::styled(b.to_string(), ratatui::style::Style::default().fg(color))
102 }
103 serde_json::Value::Null => Span::styled("null", Theme::dim()),
104 _ => Span::raw(value.to_string()),
105 }
106}