mockforge_tui/widgets/
diff_viewer.rs1use ratatui::{
4 layout::Rect,
5 style::Style,
6 text::{Line, Span},
7 widgets::{Block, Borders, Paragraph},
8 Frame,
9};
10
11use crate::theme::Theme;
12
13#[derive(Debug, Clone)]
15pub enum DiffLine {
16 Added(String),
17 Removed(String),
18 Unchanged(String),
19}
20
21pub fn render(
23 frame: &mut Frame,
24 area: Rect,
25 title: &str,
26 diff_lines: &[DiffLine],
27 scroll_offset: u16,
28) {
29 let block = Block::default()
30 .title(format!(" {title} "))
31 .title_style(Theme::title())
32 .borders(Borders::ALL)
33 .border_style(Theme::dim())
34 .style(Theme::surface());
35
36 let lines: Vec<Line> = diff_lines
37 .iter()
38 .map(|dl| match dl {
39 DiffLine::Added(text) => {
40 Line::from(Span::styled(format!("+ {text}"), Style::default().fg(Theme::GREEN)))
41 }
42 DiffLine::Removed(text) => {
43 Line::from(Span::styled(format!("- {text}"), Style::default().fg(Theme::RED)))
44 }
45 DiffLine::Unchanged(text) => {
46 Line::from(Span::styled(format!(" {text}"), Style::default().fg(Theme::FG)))
47 }
48 })
49 .collect();
50
51 if lines.is_empty() {
52 let empty = Paragraph::new(Span::styled(" No differences", Theme::dim()))
53 .block(block)
54 .scroll((scroll_offset, 0));
55 frame.render_widget(empty, area);
56 } else {
57 let paragraph = Paragraph::new(lines).block(block).scroll((scroll_offset, 0));
58 frame.render_widget(paragraph, area);
59 }
60}
61
62pub fn parse_unified_diff(text: &str) -> Vec<DiffLine> {
64 text.lines()
65 .map(|line| {
66 if let Some(rest) = line.strip_prefix('+') {
67 DiffLine::Added(rest.to_string())
68 } else if let Some(rest) = line.strip_prefix('-') {
69 DiffLine::Removed(rest.to_string())
70 } else {
71 DiffLine::Unchanged(line.strip_prefix(' ').unwrap_or(line).to_string())
72 }
73 })
74 .collect()
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn parse_empty_diff() {
83 let result = parse_unified_diff("");
84 assert!(result.is_empty());
85 }
86
87 #[test]
88 fn parse_additions_and_removals() {
89 let diff = "+added line\n-removed line\n unchanged line";
90 let result = parse_unified_diff(diff);
91 assert_eq!(result.len(), 3);
92 assert!(matches!(&result[0], DiffLine::Added(s) if s == "added line"));
93 assert!(matches!(&result[1], DiffLine::Removed(s) if s == "removed line"));
94 assert!(matches!(&result[2], DiffLine::Unchanged(s) if s == "unchanged line"));
95 }
96
97 #[test]
98 fn parse_mixed_diff() {
99 let diff = " context\n+new\n-old\n context2";
100 let result = parse_unified_diff(diff);
101 assert_eq!(result.len(), 4);
102 assert!(matches!(&result[0], DiffLine::Unchanged(s) if s == "context"));
103 assert!(matches!(&result[1], DiffLine::Added(s) if s == "new"));
104 assert!(matches!(&result[2], DiffLine::Removed(s) if s == "old"));
105 assert!(matches!(&result[3], DiffLine::Unchanged(s) if s == "context2"));
106 }
107}