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