toon_format/tui/components/
diff_viewer.rs

1//! Side-by-side diff viewer for input/output comparison.
2
3use ratatui::{
4    layout::{
5        Alignment,
6        Constraint,
7        Direction,
8        Layout,
9        Rect,
10    },
11    text::{
12        Line,
13        Span,
14    },
15    widgets::{
16        Block,
17        Borders,
18        Paragraph,
19        Wrap,
20    },
21    Frame,
22};
23
24use crate::tui::{
25    state::AppState,
26    theme::Theme,
27};
28
29pub struct DiffViewer;
30
31impl DiffViewer {
32    pub fn render(f: &mut Frame, area: Rect, app: &AppState, theme: &Theme) {
33        let block = Block::default()
34            .borders(Borders::ALL)
35            .border_style(theme.border_style(true))
36            .title(" Side-by-Side Comparison - Press Esc to close ")
37            .title_alignment(Alignment::Center);
38
39        let inner = block.inner(area);
40        f.render_widget(block, area);
41
42        let chunks = Layout::default()
43            .direction(Direction::Horizontal)
44            .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
45            .split(inner);
46
47        let input_text = app.editor.get_input();
48        let input_title = match app.mode {
49            crate::tui::state::app_state::Mode::Encode => "JSON Input",
50            crate::tui::state::app_state::Mode::Decode => "TOON Input",
51        };
52
53        let input_lines: Vec<Line> = input_text
54            .lines()
55            .enumerate()
56            .map(|(idx, line)| {
57                Line::from(vec![
58                    Span::styled(format!("{:4} ", idx + 1), theme.line_number_style()),
59                    Span::styled(line, theme.normal_style()),
60                ])
61            })
62            .collect();
63
64        let input_para = Paragraph::new(input_lines)
65            .block(
66                Block::default()
67                    .borders(Borders::ALL)
68                    .border_style(theme.border_style(false))
69                    .title(format!(" {input_title} ")),
70            )
71            .wrap(Wrap { trim: false });
72
73        f.render_widget(input_para, chunks[0]);
74
75        let output_text = app.editor.get_output();
76        let output_title = match app.mode {
77            crate::tui::state::app_state::Mode::Encode => "TOON Output",
78            crate::tui::state::app_state::Mode::Decode => "JSON Output",
79        };
80
81        let output_lines: Vec<Line> = output_text
82            .lines()
83            .enumerate()
84            .map(|(idx, line)| {
85                Line::from(vec![
86                    Span::styled(format!("{:4} ", idx + 1), theme.line_number_style()),
87                    Span::styled(line, theme.normal_style()),
88                ])
89            })
90            .collect();
91
92        let output_para = Paragraph::new(output_lines)
93            .block(
94                Block::default()
95                    .borders(Borders::ALL)
96                    .border_style(theme.border_style(false))
97                    .title(format!(" {output_title} ")),
98            )
99            .wrap(Wrap { trim: false });
100
101        f.render_widget(output_para, chunks[1]);
102    }
103}