Skip to main content

semantic_diff/ui/
mod.rs

1pub mod diff_view;
2pub mod file_tree;
3pub mod summary;
4
5use crate::app::{App, InputMode};
6use ratatui::layout::{Constraint, Layout, Rect};
7use ratatui::style::{Color, Modifier, Style};
8use ratatui::text::{Line, Span};
9use ratatui::widgets::{Block, Clear, Paragraph};
10use ratatui::Frame;
11
12/// Draw the entire UI: file tree sidebar + diff view + summary/search bar.
13pub fn draw(app: &App, frame: &mut Frame) {
14    let area = frame.area();
15
16    // Vertical split: main content area | bottom bar
17    let bottom_height = 1;
18    let vertical =
19        Layout::vertical([Constraint::Min(1), Constraint::Length(bottom_height)]).split(area);
20
21    // Horizontal split: sidebar | diff view
22    // On narrow terminals (<80 cols), use a smaller sidebar
23    let sidebar_width = if area.width < 80 {
24        Constraint::Max(25)
25    } else {
26        Constraint::Max(40)
27    };
28    let horizontal =
29        Layout::horizontal([sidebar_width, Constraint::Min(40)]).split(vertical[0]);
30
31    // Render file tree sidebar in left panel
32    file_tree::render_tree(app, frame, horizontal[0]);
33
34    // Render diff view in right panel (existing)
35    diff_view::render_diff(app, frame, horizontal[1]);
36
37    // Render bottom bar: search bar when searching, summary bar otherwise
38    match app.input_mode {
39        InputMode::Search => render_search_bar(app, frame, vertical[1]),
40        InputMode::Normal | InputMode::Help => summary::render_summary(app, frame, vertical[1]),
41    }
42
43    // Render help overlay on top if in Help mode
44    if app.input_mode == InputMode::Help {
45        render_help_overlay(frame, area);
46    }
47}
48
49/// Render the help overlay centered on screen.
50fn render_help_overlay(frame: &mut Frame, area: Rect) {
51    let shortcuts = vec![
52        ("Navigation", vec![
53            ("j/k, ↑/↓", "Move up/down"),
54            ("g/G", "Jump to top/bottom"),
55            ("Ctrl-d/u", "Half-page down/up"),
56            ("Tab", "Switch sidebar/diff focus"),
57        ]),
58        ("Actions", vec![
59            ("Enter", "Sidebar: select file/group | Diff: toggle collapse"),
60            ("/", "Search files"),
61            ("n/N", "Next/prev search match"),
62            ("Esc", "Clear filter / quit"),
63            ("q", "Quit"),
64        ]),
65    ];
66
67    let mut lines: Vec<Line> = vec![Line::raw("")];
68    for (section, keys) in &shortcuts {
69        lines.push(Line::from(Span::styled(
70            format!("  {section}"),
71            Style::default()
72                .fg(Color::Cyan)
73                .add_modifier(Modifier::BOLD),
74        )));
75        for (key, desc) in keys {
76            lines.push(Line::from(vec![
77                Span::styled(
78                    format!("    {key:<14}"),
79                    Style::default()
80                        .fg(Color::Yellow)
81                        .add_modifier(Modifier::BOLD),
82                ),
83                Span::styled(*desc, Style::default().fg(Color::White)),
84            ]));
85        }
86        lines.push(Line::raw(""));
87    }
88    lines.push(Line::from(Span::styled(
89        "  Press any key to close",
90        Style::default().fg(Color::DarkGray),
91    )));
92
93    let height = (lines.len() + 2).min(area.height as usize) as u16;
94    let width = 50u16.min(area.width.saturating_sub(4));
95    let x = (area.width.saturating_sub(width)) / 2;
96    let y = (area.height.saturating_sub(height)) / 2;
97    let popup_area = Rect::new(x, y, width, height);
98
99    frame.render_widget(Clear, popup_area);
100    let block = Block::bordered()
101        .title(" Shortcuts ")
102        .border_style(Style::default().fg(Color::Cyan));
103    let paragraph = Paragraph::new(lines).block(block);
104    frame.render_widget(paragraph, popup_area);
105}
106
107/// Render the search input bar at the bottom of the screen.
108fn render_search_bar(app: &App, frame: &mut Frame, area: ratatui::layout::Rect) {
109    let line = Line::from(vec![
110        Span::styled(
111            "/ ",
112            Style::default()
113                .fg(Color::Yellow)
114                .add_modifier(Modifier::BOLD),
115        ),
116        Span::styled(
117            app.search_query.clone(),
118            Style::default().fg(Color::White),
119        ),
120        Span::styled(
121            "_",
122            Style::default()
123                .fg(Color::White)
124                .add_modifier(Modifier::SLOW_BLINK),
125        ),
126    ]);
127    let paragraph = ratatui::widgets::Paragraph::new(line);
128    frame.render_widget(paragraph, area);
129}